|
|
@@ -23,6 +23,7 @@
|
|
|
-include_lib("emqx/include/asserts.hrl").
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
-include_lib("common_test/include/ct.hrl").
|
|
|
+-include_lib("emqx_utils/include/emqx_message.hrl").
|
|
|
|
|
|
-define(CNT, 100).
|
|
|
-define(SLEEP, 10).
|
|
|
@@ -32,25 +33,15 @@
|
|
|
|
|
|
all() ->
|
|
|
[
|
|
|
- {group, mqttv3},
|
|
|
- {group, mqttv5}
|
|
|
+ {group, persistence_disabled},
|
|
|
+ {group, persistence_enabled}
|
|
|
].
|
|
|
|
|
|
-init_per_suite(Config) ->
|
|
|
- Apps = emqx_cth_suite:start(
|
|
|
- [emqx],
|
|
|
- #{work_dir => emqx_cth_suite:work_dir(Config)}
|
|
|
- ),
|
|
|
- emqx_logger:set_log_level(debug),
|
|
|
- [{apps, Apps} | Config].
|
|
|
-
|
|
|
-end_per_suite(Config) ->
|
|
|
- Apps = ?config(apps, Config),
|
|
|
- ok = emqx_cth_suite:stop(Apps),
|
|
|
- ok.
|
|
|
-
|
|
|
groups() ->
|
|
|
+ MQTTGroups = [{group, G} || G <- [mqttv3, mqttv5]],
|
|
|
[
|
|
|
+ {persistence_enabled, MQTTGroups},
|
|
|
+ {persistence_disabled, MQTTGroups},
|
|
|
{mqttv3, [], emqx_common_test_helpers:all(?MODULE) -- tc_v5_only()},
|
|
|
{mqttv5, [], emqx_common_test_helpers:all(?MODULE)}
|
|
|
].
|
|
|
@@ -67,11 +58,55 @@ tc_v5_only() ->
|
|
|
t_takeover_session_then_abnormal_disconnect_2
|
|
|
].
|
|
|
|
|
|
+init_per_suite(Config) ->
|
|
|
+ emqx_common_test_helpers:clear_screen(),
|
|
|
+ Config.
|
|
|
+
|
|
|
+end_per_suite(_Config) ->
|
|
|
+ ok.
|
|
|
+
|
|
|
+init_per_group(persistence_enabled = Group, Config) ->
|
|
|
+ Apps = emqx_cth_suite:start(
|
|
|
+ [
|
|
|
+ {emqx,
|
|
|
+ "session_persistence = {\n"
|
|
|
+ " enable = true\n"
|
|
|
+ " last_alive_update_interval = 100ms\n"
|
|
|
+ " renew_streams_interval = 100ms\n"
|
|
|
+ " session_gc_interval = 2s\n"
|
|
|
+ "}\n"}
|
|
|
+ ],
|
|
|
+ #{work_dir => emqx_cth_suite:work_dir(Group, Config)}
|
|
|
+ ),
|
|
|
+ emqx_logger:set_log_level(debug),
|
|
|
+ [
|
|
|
+ {apps, Apps},
|
|
|
+ {persistence_enabled, true}
|
|
|
+ | Config
|
|
|
+ ];
|
|
|
+init_per_group(persistence_disabled = Group, Config) ->
|
|
|
+ Apps = emqx_cth_suite:start(
|
|
|
+ [{emqx, "session_persistence.enable = false"}],
|
|
|
+ #{work_dir => emqx_cth_suite:work_dir(Group, Config)}
|
|
|
+ ),
|
|
|
+ emqx_logger:set_log_level(debug),
|
|
|
+ [
|
|
|
+ {apps, Apps},
|
|
|
+ {persistence_enabled, false}
|
|
|
+ | Config
|
|
|
+ ];
|
|
|
init_per_group(mqttv3, Config) ->
|
|
|
lists:keystore(mqtt_vsn, 1, Config, {mqtt_vsn, v3});
|
|
|
init_per_group(mqttv5, Config) ->
|
|
|
lists:keystore(mqtt_vsn, 1, Config, {mqtt_vsn, v5}).
|
|
|
|
|
|
+end_per_group(Group, Config) when
|
|
|
+ Group =:= persistence_disabled;
|
|
|
+ Group =:= persistence_enabled
|
|
|
+->
|
|
|
+ Apps = ?config(apps, Config),
|
|
|
+ ok = emqx_cth_suite:stop(Apps),
|
|
|
+ ok;
|
|
|
end_per_group(_Group, _Config) ->
|
|
|
ok.
|
|
|
|
|
|
@@ -97,19 +132,34 @@ t_takeover(Config) ->
|
|
|
ok = timer:sleep(?SLEEP * 2),
|
|
|
meck:passthrough([Arg])
|
|
|
end),
|
|
|
+ meck:expect(emqx_cm, takeover_kick, fun(Arg) ->
|
|
|
+ %% trigger more complex takeover conditions during 2-phase takeover protocol:
|
|
|
+ %% when messages are accumulated in 2 processes simultaneously,
|
|
|
+ %% and need to be properly ordered / deduplicated after the protocol commences.
|
|
|
+ ok = timer:sleep(?SLEEP * 2),
|
|
|
+ meck:passthrough([Arg])
|
|
|
+ end),
|
|
|
Commands =
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, ClientOpts]}] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs] ++
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, ClientOpts]}] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client2Msgs] ++
|
|
|
- [{fun stop_client/1, []}],
|
|
|
+ lists:flatten([
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, ClientOpts]},
|
|
|
+ {fun maybe_wait_subscriptions/1, []},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, ClientOpts]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client2Msgs],
|
|
|
+ {fun stop_client/1, []}
|
|
|
+ ]),
|
|
|
|
|
|
+ Sleep =
|
|
|
+ case ?config(persistence_enabled, Config) of
|
|
|
+ true -> 1_500;
|
|
|
+ false -> ?SLEEP
|
|
|
+ end,
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
ct:pal("COMMAND: ~p ~p", [element(2, erlang:fun_info(Fun, name)), Args]),
|
|
|
apply(Fun, [Ctx | Args])
|
|
|
end,
|
|
|
- #{},
|
|
|
+ #{persistence_enabled => ?config(persistence_enabled, Config), sleep => Sleep},
|
|
|
Commands
|
|
|
),
|
|
|
|
|
|
@@ -117,7 +167,7 @@ t_takeover(Config) ->
|
|
|
|
|
|
assert_client_exit(CPid1, ?config(mqtt_vsn, Config), takenover),
|
|
|
?assertReceive({'EXIT', CPid2, normal}),
|
|
|
- Received = [Msg || {publish, Msg} <- ?drainMailbox(?SLEEP)],
|
|
|
+ Received = [Msg || {publish, Msg} <- ?drainMailbox(Sleep)],
|
|
|
ct:pal("middle: ~p", [Middle]),
|
|
|
ct:pal("received: ~p", [[P || #{payload := P} <- Received]]),
|
|
|
assert_messages_missed(AllMsgs, Received),
|
|
|
@@ -141,30 +191,36 @@ t_takeover_willmsg(Config) ->
|
|
|
{will_qos, 0}
|
|
|
],
|
|
|
Commands =
|
|
|
- %% GIVEN client connect with will message
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
- [
|
|
|
- {fun start_client/5, [
|
|
|
- <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
- ]}
|
|
|
- ] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs] ++
|
|
|
+ lists:flatten([
|
|
|
+ %% GIVEN client connect with will message
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun maybe_wait_subscriptions/1, []},
|
|
|
+ {fun start_client/5, [
|
|
|
+ <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
+ ]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
%% WHEN client reconnect with clean_start = false
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client2Msgs],
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client2Msgs]
|
|
|
+ ]),
|
|
|
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
ct:pal("COMMAND: ~p ~p", [element(2, erlang:fun_info(Fun, name)), Args]),
|
|
|
apply(Fun, [Ctx | Args])
|
|
|
end,
|
|
|
- #{},
|
|
|
+ #{persistence_enabled => ?config(persistence_enabled, Config)},
|
|
|
Commands
|
|
|
),
|
|
|
|
|
|
#{client := [CPid2, CPidSub, CPid1]} = FCtx,
|
|
|
assert_client_exit(CPid1, ?config(mqtt_vsn, Config), takenover),
|
|
|
- Received = [Msg || {publish, Msg} <- ?drainMailbox(?SLEEP)],
|
|
|
+ Sleep =
|
|
|
+ case ?config(persistence_enabled, Config) of
|
|
|
+ true -> 2_000;
|
|
|
+ false -> ?SLEEP
|
|
|
+ end,
|
|
|
+ Received = [Msg || {publish, Msg} <- ?drainMailbox(Sleep)],
|
|
|
ct:pal("received: ~p", [[P || #{payload := P} <- Received]]),
|
|
|
{IsWill, ReceivedNoWill} = filter_payload(Received, <<"willpayload">>),
|
|
|
assert_messages_missed(AllMsgs, ReceivedNoWill),
|
|
|
@@ -297,7 +353,7 @@ t_no_takeover_with_delayed_willmsg(Config) ->
|
|
|
?config(mqtt_vsn, Config) =:= v5 orelse ct:fail("MQTTv5 Only"),
|
|
|
process_flag(trap_exit, true),
|
|
|
ClientId = atom_to_binary(?FUNCTION_NAME),
|
|
|
- WillTopic = <<ClientId/binary, <<"willtopic">>/binary>>,
|
|
|
+ WillTopic = <<ClientId/binary, "willtopic">>,
|
|
|
Client1Msgs = messages(ClientId, 0, 10),
|
|
|
WillOpts = [
|
|
|
{proto_ver, ?config(mqtt_vsn, Config)},
|
|
|
@@ -312,24 +368,25 @@ t_no_takeover_with_delayed_willmsg(Config) ->
|
|
|
],
|
|
|
Commands =
|
|
|
%% GIVEN: client connect with willmsg payload <<"willpayload_delay3">> and delay-interval 3s
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
+ lists:flatten(
|
|
|
[
|
|
|
- {fun start_client/5, [
|
|
|
- <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
- ]}
|
|
|
- ] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun maybe_wait_subscriptions/1, []},
|
|
|
+ {fun start_client/5, [<<ClientId/binary, "_willsub">>, WillTopic, ?QOS_1, []]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs]
|
|
|
+ ]
|
|
|
+ ),
|
|
|
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
ct:pal("COMMAND: ~p ~p", [element(2, erlang:fun_info(Fun, name)), Args]),
|
|
|
apply(Fun, [Ctx | Args])
|
|
|
end,
|
|
|
- #{},
|
|
|
+ #{persistence_enabled => ?config(persistence_enabled, Config)},
|
|
|
Commands
|
|
|
),
|
|
|
|
|
|
- Received = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
+ Received = [Msg || {publish, Msg} <- ?drainMailbox(2000)],
|
|
|
ct:pal("received: ~p", [[P || #{payload := P} <- Received]]),
|
|
|
assert_messages_missed(Client1Msgs, Received),
|
|
|
{IsWill0, ReceivedNoWill0} = filter_payload(Received, <<"willpayload_delay3">>),
|
|
|
@@ -369,15 +426,15 @@ t_session_expire_with_delayed_willmsg(Config) ->
|
|
|
{properties, #{'Session-Expiry-Interval' => 3}}
|
|
|
],
|
|
|
Commands =
|
|
|
- %% GIVEN: client connect with willmsg payload <<"willpayload_delay10">>
|
|
|
- %% and delay-interval 10s > session expiry 3s.
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
- [
|
|
|
- {fun start_client/5, [
|
|
|
- <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
- ]}
|
|
|
- ] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
+ lists:flatten([
|
|
|
+ %% GIVEN: client connect with willmsg payload <<"willpayload_delay10">>
|
|
|
+ %% and delay-interval 10s > session expiry 3s.
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun start_client/5, [
|
|
|
+ <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
+ ]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs]
|
|
|
+ ]),
|
|
|
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
@@ -388,7 +445,7 @@ t_session_expire_with_delayed_willmsg(Config) ->
|
|
|
Commands
|
|
|
),
|
|
|
|
|
|
- Received = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
+ Received = [Msg || {publish, Msg} <- ?drainMailbox(2000)],
|
|
|
ct:pal("received: ~p", [[P || #{payload := P} <- Received]]),
|
|
|
{IsWill, ReceivedNoWill} = filter_payload(Received, <<"willpayload_delay10">>),
|
|
|
?assertNot(IsWill),
|
|
|
@@ -404,7 +461,15 @@ t_session_expire_with_delayed_willmsg(Config) ->
|
|
|
?assertNot(IsWill1),
|
|
|
?assertEqual([], ReceivedNoWill1),
|
|
|
%% THEN: for MQTT v5, payload <<"willpayload_delay3">> should be published after session expiry.
|
|
|
- Received2 = [Msg || {publish, Msg} <- ?drainMailbox(5000)],
|
|
|
+ SessionSleep =
|
|
|
+ case ?config(persistence_enabled, Config) of
|
|
|
+ true ->
|
|
|
+ %% Session GC uses a larger, safer cutoff time.
|
|
|
+ 10_000;
|
|
|
+ false ->
|
|
|
+ 5_000
|
|
|
+ end,
|
|
|
+ Received2 = [Msg || {publish, Msg} <- ?drainMailbox(SessionSleep)],
|
|
|
{IsWill12, ReceivedNoWill2} = filter_payload(Received2, <<"willpayload_delay10">>),
|
|
|
?assertEqual([], ReceivedNoWill2),
|
|
|
?assert(IsWill12),
|
|
|
@@ -493,25 +558,23 @@ t_takeover_before_session_expire(Config) ->
|
|
|
Commands =
|
|
|
%% GIVEN: client connect with willmsg payload <<"willpayload_delay10">>
|
|
|
%% and delay-interval 10s > session expiry 3s.
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
- [
|
|
|
- {fun start_client/5, [
|
|
|
- <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
- ]}
|
|
|
- ] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs] ++
|
|
|
- [
|
|
|
- %% avoid two clients race for session takeover
|
|
|
- {
|
|
|
- fun(CTX) ->
|
|
|
- timer:sleep(100),
|
|
|
- CTX
|
|
|
- end,
|
|
|
- []
|
|
|
- }
|
|
|
- ] ++
|
|
|
+ lists:flatten([
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun start_client/5, [
|
|
|
+ <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
+ ]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
+ %% avoid two clients race for session takeover
|
|
|
+ {
|
|
|
+ fun(CTX) ->
|
|
|
+ timer:sleep(100),
|
|
|
+ CTX
|
|
|
+ end,
|
|
|
+ []
|
|
|
+ },
|
|
|
%% WHEN: client session is taken over within 3s.
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}],
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}
|
|
|
+ ]),
|
|
|
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
@@ -540,7 +603,7 @@ t_takeover_session_then_normal_disconnect(Config) ->
|
|
|
?config(mqtt_vsn, Config) =:= v5 orelse ct:fail("MQTTv5 Only"),
|
|
|
process_flag(trap_exit, true),
|
|
|
ClientId = atom_to_binary(?FUNCTION_NAME),
|
|
|
- WillTopic = <<ClientId/binary, <<"willtopic">>/binary>>,
|
|
|
+ WillTopic = <<ClientId/binary, "willtopic">>,
|
|
|
Client1Msgs = messages(ClientId, 0, 10),
|
|
|
WillOpts = [
|
|
|
{proto_ver, ?config(mqtt_vsn, Config)},
|
|
|
@@ -552,40 +615,42 @@ t_takeover_session_then_normal_disconnect(Config) ->
|
|
|
{properties, #{'Session-Expiry-Interval' => 3}}
|
|
|
],
|
|
|
Commands =
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
- [
|
|
|
- {fun start_client/5, [
|
|
|
- <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
- ]}
|
|
|
- ] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs] ++
|
|
|
- [
|
|
|
- %% avoid two clients race for session takeover
|
|
|
- {
|
|
|
- fun(CTX) ->
|
|
|
- timer:sleep(100),
|
|
|
- CTX
|
|
|
- end,
|
|
|
- []
|
|
|
- }
|
|
|
- ] ++
|
|
|
+ lists:flatten([
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun maybe_wait_subscriptions/1, []},
|
|
|
+ {fun start_client/5, [
|
|
|
+ <<ClientId/binary, "_willsub">>, WillTopic, ?QOS_1, []
|
|
|
+ ]},
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
+ %% avoid two clients race for session takeover
|
|
|
+ {
|
|
|
+ fun(CTX) ->
|
|
|
+ timer:sleep(100),
|
|
|
+ CTX
|
|
|
+ end,
|
|
|
+ []
|
|
|
+ },
|
|
|
%% GIVEN: client reconnect with willmsg payload <<"willpayload_delay10">>
|
|
|
%% and delay-interval 10s > session expiry 3s.
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}],
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun maybe_wait_subscriptions/1, []}
|
|
|
+ ]),
|
|
|
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
ct:pal("COMMAND: ~p ~p", [element(2, erlang:fun_info(Fun, name)), Args]),
|
|
|
apply(Fun, [Ctx | Args])
|
|
|
end,
|
|
|
- #{},
|
|
|
+ #{persistence_enabled => ?config(persistence_enabled, Config)},
|
|
|
Commands
|
|
|
),
|
|
|
#{client := [CPid2, CPidSub, CPid1]} = FCtx,
|
|
|
assert_client_exit(CPid1, ?config(mqtt_vsn, Config), takenover),
|
|
|
+ Received1 = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
%% WHEN: client disconnect normally.
|
|
|
emqtt:disconnect(CPid2, ?RC_SUCCESS),
|
|
|
- Received = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
+ Received2 = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
+ Received = Received1 ++ Received2,
|
|
|
ct:pal("received: ~p", [[P || #{payload := P} <- Received]]),
|
|
|
{IsWill, ReceivedNoWill} = filter_payload(Received, <<"willpayload_delay10">>),
|
|
|
%% THEN: willmsg is not published.
|
|
|
@@ -600,7 +665,7 @@ t_takeover_session_then_abnormal_disconnect(Config) ->
|
|
|
?config(mqtt_vsn, Config) =:= v5 orelse ct:fail("MQTTv5 Only"),
|
|
|
process_flag(trap_exit, true),
|
|
|
ClientId = atom_to_binary(?FUNCTION_NAME),
|
|
|
- WillTopic = <<ClientId/binary, <<"willtopic">>/binary>>,
|
|
|
+ WillTopic = <<ClientId/binary, "willtopic">>,
|
|
|
Client1Msgs = messages(ClientId, 0, 10),
|
|
|
WillOpts = [
|
|
|
{proto_ver, ?config(mqtt_vsn, Config)},
|
|
|
@@ -612,26 +677,24 @@ t_takeover_session_then_abnormal_disconnect(Config) ->
|
|
|
{properties, #{'Session-Expiry-Interval' => 3}}
|
|
|
],
|
|
|
Commands =
|
|
|
- %% GIVEN: client connect with willmsg payload <<"willpayload_delay10">>
|
|
|
- %% and will-delay-interval 10s > session expiry 3s.
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}] ++
|
|
|
- [
|
|
|
- {fun start_client/5, [
|
|
|
- <<ClientId/binary, <<"_willsub">>/binary>>, WillTopic, ?QOS_1, []
|
|
|
- ]}
|
|
|
- ] ++
|
|
|
- [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs] ++
|
|
|
- [
|
|
|
- %% avoid two clients race for session takeover
|
|
|
- {
|
|
|
- fun(CTX) ->
|
|
|
- timer:sleep(100),
|
|
|
- CTX
|
|
|
- end,
|
|
|
- []
|
|
|
- }
|
|
|
- ] ++
|
|
|
- [{fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}],
|
|
|
+ lists:flatten([
|
|
|
+ %% GIVEN: client connect with willmsg payload <<"willpayload_delay10">>
|
|
|
+ %% and will-delay-interval 10s > session expiry 3s.
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]},
|
|
|
+ {fun start_client/5, [
|
|
|
+ <<ClientId/binary, "_willsub">>, WillTopic, ?QOS_1, []
|
|
|
+ ]},
|
|
|
+ %% avoid two clients race for session takeover
|
|
|
+ {
|
|
|
+ fun(CTX) ->
|
|
|
+ timer:sleep(100),
|
|
|
+ CTX
|
|
|
+ end,
|
|
|
+ []
|
|
|
+ },
|
|
|
+ [{fun publish_msg/2, [Msg]} || Msg <- Client1Msgs],
|
|
|
+ {fun start_client/5, [ClientId, ClientId, ?QOS_1, WillOpts]}
|
|
|
+ ]),
|
|
|
|
|
|
FCtx = lists:foldl(
|
|
|
fun({Fun, Args}, Ctx) ->
|
|
|
@@ -643,16 +706,26 @@ t_takeover_session_then_abnormal_disconnect(Config) ->
|
|
|
),
|
|
|
#{client := [CPid2, CPidSub, CPid1]} = FCtx,
|
|
|
assert_client_exit(CPid1, ?config(mqtt_vsn, Config), takenover),
|
|
|
+ Received1 = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
%% WHEN: client disconnect abnormally
|
|
|
emqtt:disconnect(CPid2, ?RC_DISCONNECT_WITH_WILL_MESSAGE),
|
|
|
- Received = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
+ Received2 = [Msg || {publish, Msg} <- ?drainMailbox(1000)],
|
|
|
+ Received = Received1 ++ Received2,
|
|
|
ct:pal("received: ~p", [[P || #{payload := P} <- Received]]),
|
|
|
{IsWill, ReceivedNoWill} = filter_payload(Received, <<"willpayload_delay10">>),
|
|
|
%% THEN: willmsg is not published before session expiry
|
|
|
?assertNot(IsWill),
|
|
|
?assertNotEqual([], ReceivedNoWill),
|
|
|
- Received1 = [Msg || {publish, Msg} <- ?drainMailbox(3000)],
|
|
|
- {IsWill1, ReceivedNoWill1} = filter_payload(Received1, <<"willpayload_delay10">>),
|
|
|
+ SessionSleep =
|
|
|
+ case ?config(persistence_enabled, Config) of
|
|
|
+ true ->
|
|
|
+ %% Session GC uses a larger, safer cutoff time (GC interval x 3)
|
|
|
+ 10_000;
|
|
|
+ false ->
|
|
|
+ 3_000
|
|
|
+ end,
|
|
|
+ Received3 = [Msg || {publish, Msg} <- ?drainMailbox(SessionSleep)],
|
|
|
+ {IsWill1, ReceivedNoWill1} = filter_payload(Received3, <<"willpayload_delay10">>),
|
|
|
%% AND THEN: willmsg is published after session expiry
|
|
|
?assert(IsWill1),
|
|
|
?assertEqual([], ReceivedNoWill1),
|
|
|
@@ -866,19 +939,39 @@ start_client(Ctx, ClientId, Topic, Qos, Opts) ->
|
|
|
end),
|
|
|
Ctx#{client => [CPid | maps:get(client, Ctx, [])]}.
|
|
|
|
|
|
+maybe_wait_subscriptions(Ctx = #{persistence_enabled := true, client := CPids}) ->
|
|
|
+ ok = do_wait_subscription(CPids),
|
|
|
+ Ctx;
|
|
|
+maybe_wait_subscriptions(Ctx) ->
|
|
|
+ Ctx.
|
|
|
+
|
|
|
+do_wait_subscription([]) ->
|
|
|
+ ok;
|
|
|
+do_wait_subscription([CPid | Rest]) ->
|
|
|
+ try emqtt:subscriptions(CPid) of
|
|
|
+ [] ->
|
|
|
+ ok = timer:sleep(rand:uniform(?SLEEP)),
|
|
|
+ do_wait_subscription([CPid | Rest]);
|
|
|
+ [_ | _] ->
|
|
|
+ do_wait_subscription(Rest)
|
|
|
+ catch
|
|
|
+ exit:{noproc, _} ->
|
|
|
+ ok
|
|
|
+ end.
|
|
|
+
|
|
|
kick_client(Ctx, ClientId) ->
|
|
|
ok = emqx_cm:kick_session(ClientId),
|
|
|
Ctx.
|
|
|
|
|
|
publish_msg(Ctx, Msg) ->
|
|
|
ok = timer:sleep(rand:uniform(?SLEEP)),
|
|
|
- case emqx:publish(Msg) of
|
|
|
+ case emqx:publish(Msg#message{timestamp = emqx_message:timestamp_now()}) of
|
|
|
[] -> publish_msg(Ctx, Msg);
|
|
|
[_ | _] -> Ctx
|
|
|
end.
|
|
|
|
|
|
-stop_client(Ctx = #{client := [CPid | _]}) ->
|
|
|
- ok = timer:sleep(?SLEEP),
|
|
|
+stop_client(Ctx = #{client := [CPid | _], sleep := Sleep}) ->
|
|
|
+ ok = timer:sleep(Sleep),
|
|
|
ok = emqtt:stop(CPid),
|
|
|
Ctx.
|
|
|
|
|
|
@@ -904,14 +997,18 @@ assert_messages_missed(Ls1, Ls2) ->
|
|
|
error
|
|
|
end.
|
|
|
|
|
|
-assert_messages_order([], []) ->
|
|
|
+assert_messages_order([] = _Expected, _Received) ->
|
|
|
ok;
|
|
|
assert_messages_order([Msg | Expected], Received) ->
|
|
|
%% Account for duplicate messages:
|
|
|
case lists:splitwith(fun(#{payload := P}) -> emqx_message:payload(Msg) == P end, Received) of
|
|
|
- {[], [#{payload := Mismatch} | _]} ->
|
|
|
+ {[], [#{timestamp := TSMismatch, payload := Mismatch} | _]} ->
|
|
|
ct:fail("Message order is not correct, expected: ~p, received: ~p", [
|
|
|
- emqx_message:payload(Msg), Mismatch
|
|
|
+ {
|
|
|
+ emqx_utils_calendar:epoch_to_rfc3339(emqx_message:timestamp(Msg)),
|
|
|
+ emqx_message:payload(Msg)
|
|
|
+ },
|
|
|
+ {emqx_utils_calendar:epoch_to_rfc3339(TSMismatch), Mismatch}
|
|
|
]),
|
|
|
error;
|
|
|
{_Matching, Rest} ->
|