feat(ratelimit): refactor ratelimit

This commit is contained in:
JianBo He 2020-07-21 10:38:10 +08:00 committed by tigercl
parent 961e7b9818
commit 83a2af812f
11 changed files with 268 additions and 136 deletions

View File

@ -680,12 +680,6 @@ mqtt.strict_mode = false
## Value: duration
zone.external.idle_timeout = 15s
## Publish limit for the external MQTT connections.
##
## Value: Number,Duration
## Example: 100 messages per 10 seconds.
## zone.external.publish_limit = 100,10s
## Enable ACL check.
##
## Value: Flag
@ -848,6 +842,28 @@ zone.external.mqueue_store_qos0 = true
## Value: on | off
zone.external.enable_flapping_detect = off
## Message limit for the a external MQTT connection.
##
## Value: Number,Duration
## Example: 100 messages per 10 seconds.
#zone.external.conn_rate_limit.messages_in = 100, 10s
## Bytes limit for a external MQTT connections.
##
## Value: Number,Duration
## Example: 100KB incoming per 10 seconds.
#zone.external.conn_rate_limit.bytes_in = 100KB, 10s
## Message limit for the all external MQTT connections.
##
## Value: Number,Duration
#zone.external.overall_rate_limit.messages_in = 200000, 1s
## Bytes limit for the all external MQTT connections.
##
## Value: Number,Duration
#zone.external.overall_rate_limit.bytes_in = 2048MB, 1s
## All the topics will be prefixed with the mountpoint path if this option is enabled.
##
## Variables in mountpoint path:
@ -1016,12 +1032,6 @@ listener.tcp.external.active_n = 100
## Value: String
listener.tcp.external.zone = external
## Rate limit for the external MQTT/TCP connections. Format is 'limit,duration'.
##
## Value: limit,duration
## Default: 100KB incoming per 10 seconds.
## listener.tcp.external.rate_limit = 100KB,10s
## The access control rules for the MQTT/TCP listener.
##
## See: https://github.com/emqtt/esockd#allowdeny
@ -1145,14 +1155,6 @@ listener.tcp.internal.active_n = 1000
## Value: String
listener.tcp.internal.zone = internal
## Rate limit for the internal MQTT/TCP connections.
##
## See: listener.tcp.$name.rate_limit
##
## Value: limit,duration
## Default: 1MB incoming per second.
## listener.tcp.internal.rate_limit = 1MB,1s
## The TCP backlog of internal MQTT/TCP Listener.
##
## See: listener.tcp.$name.backlog
@ -1257,12 +1259,6 @@ listener.ssl.external.zone = external
## Value: ACL Rule
listener.ssl.external.access.1 = allow all
## Rate limit for the external MQTT/SSL connections.
##
## Value: limit,duration
## Default: 100KB incoming per 10 seconds.
## listener.ssl.external.rate_limit = 100KB,10s
## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind
## HAProxy or Nginx.
##
@ -1496,12 +1492,6 @@ listener.ws.external.max_conn_rate = 1000
## Value: Number
listener.ws.external.active_n = 100
## Rate limit for the MQTT/WebSocket connections.
##
## Value: Limit,Duration
## Default: 100KB incoming per 10 seconds.
## listener.ws.external.rate_limit = 100KB,10s
## Zone of the external MQTT/WebSocket listener belonged to.
##
## Value: String
@ -1697,12 +1687,6 @@ listener.wss.external.max_conn_rate = 1000
## Value: Number
listener.wss.external.active_n = 100
## Rate limit for the MQTT/WebSocket/SSL connections.
##
## Value: Limit,Duration
## Default: 100KB incoming per 10 seconds.
## listener.wss.external.rate_limit = 100KB,10s
## Zone of the external MQTT/WebSocket/SSL listener belonged to.
##
## Value: String

View File

@ -992,6 +992,22 @@ end}.
{default, off}
]}.
{mapping, "zone.$name.conn_rate_limit.messages_in", "emqx.zones", [
{datatype, string}
]}.
{mapping, "zone.$name.conn_rate_limit.bytes_in", "emqx.zones", [
{datatype, string}
]}.
{mapping, "zone.$name.overall_rate_limit.messages_in", "emqx.zones", [
{datatype, string}
]}.
{mapping, "zone.$name.overall_rate_limit.bytes_in", "emqx.zones", [
{datatype, string}
]}.
%% @doc Force connection/session process GC after this number of
%% messages | bytes passed through.
%% Numbers delimited by `|'. Zero or negative is to disable.
@ -1036,16 +1052,22 @@ end}.
]}.
{translation, "emqx.zones", fun(Conf) ->
Mapping = fun("publish_limit", Val) ->
[L, D] = string:tokens(Val, ", "),
Limit = list_to_integer(L),
Duration = case cuttlefish_duration:parse(D, s) of
Secs when is_integer(Secs) -> Secs;
{error, Reason} -> error(Reason)
end,
Rate = Limit / Duration,
{publish_limit, {Rate, Limit}};
("force_gc_policy", Val) ->
Ratelimit = fun(Val) ->
[L, D] = string:tokens(Val, ", "),
Limit = case cuttlefish_bytesize:parse(L) of
Sz when is_integer(Sz) -> Sz;
{error, Reason1} -> error(Reason1)
end,
Duration = case cuttlefish_duration:parse(D, s) of
Secs when is_integer(Secs) -> Secs;
{error, Reason} -> error(Reason)
end,
{Limit, Duration}
end,
Mapping = fun(["publish_limit"], Val) ->
%% XXX: Deprecated at v4.2
{publish_limit, Ratelimit(Val)};
(["force_gc_policy"], Val) ->
[Count, Bytes] = string:tokens(Val, "| "),
GcPolicy = case cuttlefish_bytesize:parse(Bytes) of
{error, Reason} ->
@ -1055,7 +1077,7 @@ end}.
count => list_to_integer(Count)}
end,
{force_gc_policy, GcPolicy};
("force_shutdown_policy", Val) ->
(["force_shutdown_policy"], Val) ->
[Len, Siz] = string:tokens(Val, "| "),
MaxSiz = case WordSize = erlang:system_info(wordsize) of
8 -> % arch_64
@ -1074,7 +1096,7 @@ end}.
max_heap_size => Siz1 div WordSize}
end,
{force_shutdown_policy, ShutdownPolicy};
("mqueue_priorities", Val) ->
(["mqueue_priorities"], Val) ->
case Val of
"none" -> {mqueue_priorities, none}; % NO_PRIORITY_TABLE
_ ->
@ -1087,19 +1109,35 @@ end}.
end, #{}, string:tokens(Val, ",")),
{mqueue_priorities, MqueuePriorities}
end;
("mountpoint", Val) ->
(["mountpoint"], Val) ->
{mountpoint, iolist_to_binary(Val)};
("response_information", Val) ->
(["response_information"], Val) ->
{response_information, iolist_to_binary(Val)};
(Opt, Val) ->
(["conn_rate_limit", "messages_in"], Val) ->
{ratelimit, {conn_messages_in, Ratelimit(Val)}};
(["conn_rate_limit", "bytes_in"], Val) ->
{ratelimit, {conn_bytes_in, Ratelimit(Val)}};
(["overall_rate_limit", "messages_in"], Val) ->
{ratelimit, {overall_messages_in, Ratelimit(Val)}};
(["overall_rate_limit", "bytes_in"], Val) ->
{ratelimit, {overall_bytes_in, Ratelimit(Val)}};
([Opt], Val) ->
{list_to_atom(Opt), Val}
end,
maps:to_list(
lists:foldl(
fun({["zone", Name, Opt], Val}, Zones) ->
fun({["zone", Name | Opt], Val}, Zones) ->
NVal = Mapping(Opt, Val),
maps:update_with(list_to_atom(Name),
fun(Opts) -> [Mapping(Opt, Val)|Opts] end,
[Mapping(Opt, Val)], Zones)
fun(Opts) ->
case NVal of
{ratelimit, Rl} ->
Rls = proplists:get_value(ratelimit, Opts, []),
lists:keystore(ratelimit, 1, Opts, {ratelimit, [Rl|Rls]});
_ ->
[NVal|Opts]
end
end, [NVal], Zones)
end, #{}, lists:usort(cuttlefish_variable:filter_by_prefix("zone.", Conf))))
end}.
@ -1736,8 +1774,7 @@ end}.
Secs when is_integer(Secs) -> Secs;
{error, Reason1} -> error(Reason1)
end,
Rate = Limit / Duration,
{Rate, Limit}
{Limit, Duration}
end,
LisOpts = fun(Prefix) ->

View File

@ -6,7 +6,7 @@
[{gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}},
{jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}},
{cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.7.1"}}},
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.7.1"}}},
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.7.2"}}},
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.7.3"}}},
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.4.1"}}},
{cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}

View File

@ -198,7 +198,9 @@ init_state(Transport, Socket, Options) ->
Zone = proplists:get_value(zone, Options),
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N),
PubLimit = emqx_zone:publish_limit(Zone),
Limiter = emqx_limiter:init([{pub_limit, PubLimit}|Options]),
BytesIn = proplists:get_value(rate_limit, Options),
RateLimit = emqx_zone:ratelimit(Zone),
Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit),
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(FrameOpts),
Serialize = emqx_frame:serialize_fun(),

View File

@ -18,55 +18,137 @@
-include("types.hrl").
-export([init/1, info/1, check/2]).
-import(emqx_misc, [maybe_apply/2]).
-export([ init/2
, init/4 %% XXX: Compatible with before 4.2 version
, info/1
, check/2
]).
-record(limiter, {
%% Publish Limit
pub_limit :: maybe(esockd_rate_limit:bucket()),
%% Rate Limit
rate_limit :: maybe(esockd_rate_limit:bucket())
%% Zone
zone :: emqx_zone:zone(),
%% All checkers
checkers :: [checker()]
}).
-type(checker() :: #{ name := name()
, capacity := non_neg_integer()
, interval := non_neg_integer()
, consumer := function() | esockd_rate_limit:bucket()
}).
-type(name() :: conn_bytes_in
| conn_messages_in
| overall_bytes_in
| overall_messages_in
).
-type(spec() :: {name(), esockd_rate_limit:config()}).
-type(specs() :: [spec()]).
-type(info() :: #{name() :=
#{tokens := non_neg_integer(),
capacity := non_neg_integer(),
interval := non_neg_integer()}}).
-type(limiter() :: #limiter{}).
-export_type([limiter/0]).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
-define(ENABLED(Rl), (Rl =/= undefined)).
-spec(init(emqx_zone:zone(),
maybe(esockd_rate_limit:config()),
maybe(esockd_rate_limit:config()), specs())
-> maybe(limiter())).
init(Zone, PubLimit, BytesIn, RateLimit) ->
Merged = maps:merge(#{conn_messages_in => PubLimit,
conn_bytes_in => BytesIn}, maps:from_list(RateLimit)),
Filtered = maps:filter(fun(_, V) -> V /= undefined end, Merged),
init(Zone, maps:to_list(Filtered)).
-spec(init(proplists:proplist()) -> maybe(limiter())).
init(Options) ->
Pl = proplists:get_value(pub_limit, Options),
Rl = proplists:get_value(rate_limit, Options),
case ?ENABLED(Pl) or ?ENABLED(Rl) of
true -> #limiter{pub_limit = init_limit(Pl),
rate_limit = init_limit(Rl)
};
false -> undefined
-spec(init(emqx_zone:zone(), specs()) -> maybe(limiter())).
init(_Zone, []) ->
undefined;
init(Zone, Specs) ->
#limiter{zone = Zone, checkers = [do_init_checker(Zone, Spec) || Spec <- Specs]}.
%% @private
do_init_checker(Zone, {Name, {Capacity, Interval}}) ->
Ck = #{name => Name, capacity => Capacity, interval => Interval},
case is_overall_limiter(Name) of
true ->
case catch esockd_limiter:lookup({Zone, Name}) of
_Info when is_map(_Info) ->
ignore;
_ ->
esockd_limiter:create({Zone, Name}, Capacity, Interval)
end,
Ck#{consumer => fun(I) -> esockd_limiter:consume({Zone, Name}, I) end};
_ ->
Ck#{consumer => esockd_rate_limit:new(Capacity / Interval, Capacity)}
end.
init_limit(Rl) ->
maybe_apply(fun esockd_rate_limit:new/1, Rl).
-spec(info(limiter()) -> info()).
info(#limiter{zone = Zone, checkers = Cks}) ->
maps:from_list([get_info(Zone, Ck) || Ck <- Cks]).
info(#limiter{pub_limit = Pl, rate_limit = Rl}) ->
#{pub_limit => info(Pl), rate_limit => info(Rl)};
info(Rl) ->
maybe_apply(fun esockd_rate_limit:info/1, Rl).
check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{pub_limit = Pl,
rate_limit = Rl}) ->
do_check([{#limiter.pub_limit, Cnt, Pl} || ?ENABLED(Pl)] ++
[{#limiter.rate_limit, Oct, Rl} || ?ENABLED(Rl)], Limiter).
do_check([], Limiter) ->
{ok, Limiter};
do_check([{Pos, Cnt, Rl}|More], Limiter) ->
case esockd_rate_limit:check(Cnt, Rl) of
{0, Rl1} ->
do_check(More, setelement(Pos, Limiter, Rl1));
{Pause, Rl1} ->
{pause, Pause, setelement(Pos, Limiter, Rl1)}
-spec(check(#{cnt := Cnt :: non_neg_integer(),
oct := Oct :: non_neg_integer()},
Limiter :: limiter())
-> {ok, NLimiter :: limiter()}
| {pause, MilliSecs :: non_neg_integer(), NLimiter :: limiter()}).
check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{checkers = Cks}) ->
{Pauses, NCks} = do_check(Cnt, Oct, Cks, [], []),
case lists:max(Pauses) of
I when I > 0 ->
{pause, I, Limiter#limiter{checkers = NCks}};
_ ->
{ok, Limiter#limiter{checkers = NCks}}
end.
%% @private
do_check(_, _, [], Pauses, NCks) ->
{Pauses, lists:reverse(NCks)};
do_check(Pubs, Bytes, [Ck|More], Pauses, Acc) ->
{I, NConsumer} = consume(Pubs, Bytes, Ck),
do_check(Pubs, Bytes, More, [I|Pauses], [Ck#{consumer := NConsumer}|Acc]).
%%--------------------------------------------------------------------
%% Internal funcs
%%--------------------------------------------------------------------
consume(Pubs, Bytes, #{name := Name, consumer := Cons}) ->
Tokens = case is_message_limiter(Name) of true -> Pubs; _ -> Bytes end,
case Tokens =:= 0 of
true ->
{0, Cons};
_ ->
case is_overall_limiter(Name) of
true ->
{_, Intv} = Cons(Tokens),
{Intv, Cons};
_ ->
esockd_rate_limit:check(Tokens, Cons)
end
end.
get_info(Zone, #{name := Name, capacity := Cap,
interval := Intv, consumer := Cons}) ->
Info = case is_overall_limiter(Name) of
true -> esockd_limiter:lookup({Zone, Name});
_ -> esockd_rate_limit:info(Cons)
end,
{Name, #{capacity => Cap,
interval => Intv,
tokens => maps:get(tokens, Info)}}.
is_overall_limiter(overall_bytes_in) -> true;
is_overall_limiter(overall_messages_in) -> true;
is_overall_limiter(_) -> false.
is_message_limiter(conn_messages_in) -> true;
is_message_limiter(overall_messages_in) -> true;
is_message_limiter(_) -> false.

View File

@ -222,7 +222,9 @@ websocket_init([Req, Opts]) ->
},
Zone = proplists:get_value(zone, Opts),
PubLimit = emqx_zone:publish_limit(Zone),
Limiter = emqx_limiter:init([{pub_limit, PubLimit}|Opts]),
BytesIn = proplists:get_value(rate_limit, Opts),
RateLimit = emqx_zone:ratelimit(Zone),
Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit),
ActiveN = proplists:get_value(active_n, Opts, ?ACTIVE_N),
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(FrameOpts),

View File

@ -28,6 +28,7 @@
-compile({inline,
[ idle_timeout/1
, publish_limit/1
, ratelimit/1
, mqtt_frame_options/1
, mqtt_strict_mode/1
, max_packet_size/1
@ -55,7 +56,9 @@
%% Zone Option API
-export([ idle_timeout/1
%% XXX: Dedeprecated at v4.2
, publish_limit/1
, ratelimit/1
, mqtt_frame_options/1
, mqtt_strict_mode/1
, max_packet_size/1
@ -137,6 +140,10 @@ idle_timeout(Zone) ->
publish_limit(Zone) ->
get_env(Zone, publish_limit).
-spec(ratelimit(zone()) -> [emqx_limiter:specs()]).
ratelimit(Zone) ->
get_env(Zone, ratelimit, []).
-spec(mqtt_frame_options(zone()) -> emqx_frame:options()).
mqtt_frame_options(Zone) ->
#{strict_mode => mqtt_strict_mode(Zone),

View File

@ -104,7 +104,7 @@ t_info(_) ->
socktype := tcp}, SockInfo).
t_info_limiter(_) ->
St = st(#{limiter => emqx_limiter:init([])}),
St = st(#{limiter => emqx_limiter:init(external, [])}),
?assertEqual(undefined, emqx_connection:info(limiter, St)).
t_stats(_) ->
@ -279,11 +279,11 @@ t_ensure_rate_limit(_) ->
State = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => undefined})),
?assertEqual(undefined, emqx_connection:info(limiter, State)),
ok = meck:expect(emqx_limiter, check, fun(_, _) -> {ok, emqx_limiter:init([])} end),
ok = meck:expect(emqx_limiter, check, fun(_, _) -> {ok, emqx_limiter:init(external, [])} end),
State1 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})),
?assertEqual(undefined, emqx_connection:info(limiter, State1)),
ok = meck:expect(emqx_limiter, check, fun(_, _) -> {pause, 3000, emqx_limiter:init([])} end),
ok = meck:expect(emqx_limiter, check, fun(_, _) -> {pause, 3000, emqx_limiter:init(external, [])} end),
State2 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})),
?assertEqual(undefined, emqx_connection:info(limiter, State2)),
?assertEqual(blocked, emqx_connection:info(sockstate, State2)).

View File

@ -21,38 +21,57 @@
-include_lib("eunit/include/eunit.hrl").
%%--------------------------------------------------------------------
%% Setups
%%--------------------------------------------------------------------
all() -> emqx_ct:all(?MODULE).
init_per_testcase(_TestCase, Config) ->
Config.
init_per_testcase(_, Cfg) ->
{ok, _} = esockd_limiter:start_link(),
Cfg.
end_per_testcase(_TestCase, _Config) ->
ok.
end_per_testcase(_, _) ->
esockd_limiter:stop().
t_info(_) ->
#{pub_limit := #{rate := 1,
burst := 10,
tokens := 10
},
rate_limit := #{rate := 100,
burst := 1000,
tokens := 1000
}
} = emqx_limiter:info(limiter()).
%%--------------------------------------------------------------------
%% Cases
%%--------------------------------------------------------------------
t_check(_) ->
lists:foreach(fun(I) ->
{ok, Limiter} = emqx_limiter:check(#{cnt => I, oct => I*100}, limiter()),
#{pub_limit := #{tokens := Cnt},
rate_limit := #{tokens := Oct}
} = emqx_limiter:info(Limiter),
?assertEqual({10 - I, 1000 - I*100}, {Cnt, Oct})
end, lists:seq(1, 10)).
t_init(_) ->
Cap1 = 1000, Intv1 = 10,
Cap2 = 2000, Intv2 = 15,
undefined = emqx_limiter:init(external, undefined, undefined, []),
?assertEqual(emqx_limiter:init(external, undefined, undefined, [{conn_messages_in, {Cap1, Intv1}},
{conn_bytes_in, {Cap2, Intv2}}]),
emqx_limiter:init(external, {Cap1, Intv1}, {Cap2, Intv2}, [])),
#{conn_bytes_in := #{capacity := Cap2, interval := Intv2, tokens := Cap2 }} =
emqx_limiter:info(
emqx_limiter:init(external, undefined, {Cap1, Intv1}, [{conn_bytes_in, {Cap2, Intv2}}])).
t_check_pause(_) ->
{pause, 1000, _} = emqx_limiter:check(#{cnt => 11, oct => 2000}, limiter()),
{pause, 2000, _} = emqx_limiter:check(#{cnt => 10, oct => 1200}, limiter()).
t_check_conn(_) ->
Limiter = emqx_limiter:init(external, [{conn_bytes_in, {100, 1}}]),
limiter() ->
emqx_limiter:init([{pub_limit, {1, 10}}, {rate_limit, {100, 1000}}]).
{ok, Limiter2} = emqx_limiter:check(#{cnt => 0, oct => 1}, Limiter),
#{conn_bytes_in := #{tokens := 99}} = emqx_limiter:info(Limiter2),
{pause, 10, Limiter3} = emqx_limiter:check(#{cnt => 0, oct => 100}, Limiter),
#{conn_bytes_in := #{tokens := 0}} = emqx_limiter:info(Limiter3),
{pause, 100000, Limiter4} = emqx_limiter:check(#{cnt => 0, oct => 10000}, Limiter3),
#{conn_bytes_in := #{tokens := 0}} = emqx_limiter:info(Limiter4).
t_check_overall(_) ->
Limiter = emqx_limiter:init(external, [{overall_bytes_in, {100, 1}}]),
{ok, Limiter2} = emqx_limiter:check(#{cnt => 0, oct => 1}, Limiter),
#{overall_bytes_in := #{tokens := 99}} = emqx_limiter:info(Limiter2),
%% XXX: P = 1/r = 1/100 * 1000 = 10ms ?
{pause, 1000, Limiter3} = emqx_limiter:check(#{cnt => 0, oct => 100}, Limiter),
#{overall_bytes_in := #{tokens := 0}} = emqx_limiter:info(Limiter2),
%% XXX: P = 10000/r = 10000/100 * 1000 = 100s ?
{pause, 1000, Limiter4} = emqx_limiter:check(#{cnt => 0, oct => 10000}, Limiter3),
#{overall_bytes_in := #{tokens := 0}} = emqx_limiter:info(Limiter4).

View File

@ -112,7 +112,7 @@ t_info(_) ->
} = SockInfo.
t_info_limiter(_) ->
St = st(#{limiter => emqx_limiter:init([])}),
St = st(#{limiter => emqx_limiter:init(external, [])}),
?assertEqual(undefined, ?ws_conn:info(limiter, St)).
t_info_channel(_) ->
@ -291,9 +291,7 @@ t_handle_timeout_emit_stats(_) ->
?assertEqual(undefined, ?ws_conn:info(stats_timer, St)).
t_ensure_rate_limit(_) ->
Limiter = emqx_limiter:init([{pub_limit, {1, 10}},
{rate_limit, {100, 1000}}
]),
Limiter = emqx_limiter:init(external, {1, 10}, {100, 1000}, []),
St = st(#{limiter => Limiter}),
St1 = ?ws_conn:ensure_rate_limit(#{cnt => 0, oct => 0}, St),
St2 = ?ws_conn:ensure_rate_limit(#{cnt => 11, oct => 1200}, St1),

View File

@ -193,6 +193,7 @@ t_batch_subscribe(_) ->
?RC_NO_SUBSCRIPTION_EXISTED]} = emqtt:unsubscribe(Client, [<<"t1">>,
<<"t2">>,
<<"t3">>]),
file:delete(TempAcl),
emqtt:disconnect(Client).
t_connect_will_retain(_) ->
@ -253,7 +254,7 @@ t_connect_limit_timeout(_) ->
end),
Topic = nth(1, ?TOPICS),
emqx_zone:set_env(external, publish_limit, {2.0, 3}),
emqx_zone:set_env(external, publish_limit, {3, 5}),
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
{ok, _} = emqtt:connect(Client),
@ -263,11 +264,11 @@ t_connect_limit_timeout(_) ->
ok = emqtt:publish(Client, Topic, <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 0),
ok = emqtt:publish(Client, Topic, <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 0),
ok = emqtt:publish(Client, Topic, <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 0),
ok = emqtt:publish(Client, Topic, <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 0),
timer:sleep(200),
?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))),
ok = emqtt:disconnect(Client),
emqx_zone:set_env(external, publish_limit, undefined),
meck:unload(proplists).
t_connect_emit_stats_timeout(_) ->