Compare commits
69 Commits
master
...
mqtt-frame
Author | SHA1 | Date |
---|---|---|
![]() |
a2f9384dfc | |
![]() |
c5c3f78c5a | |
![]() |
3505e286ba | |
![]() |
2b17d6e297 | |
![]() |
b04160e09d | |
![]() |
c13813538e | |
![]() |
0c6e83f08e | |
![]() |
ec40bac20f | |
![]() |
8bb9ad7206 | |
![]() |
8cdfb531a7 | |
![]() |
cb6bb9ede5 | |
![]() |
a3095fa91e | |
![]() |
348e6b8f10 | |
![]() |
9c84bb5e87 | |
![]() |
3e69124ca0 | |
![]() |
02b8b0ec08 | |
![]() |
cf3354d30d | |
![]() |
80608e9c99 | |
![]() |
9969fd0d18 | |
![]() |
93ec2ef995 | |
![]() |
dc6bc76512 | |
![]() |
f09bf74c99 | |
![]() |
0f1b14f865 | |
![]() |
e57c30a0b9 | |
![]() |
47b2642423 | |
![]() |
a0a44eecb5 | |
![]() |
cf9d82073c | |
![]() |
a11208b307 | |
![]() |
fb7c84a5b8 | |
![]() |
edb2c5f3c1 | |
![]() |
3d7f4335a0 | |
![]() |
915c827fdc | |
![]() |
649bf2f4cb | |
![]() |
437837d687 | |
![]() |
26b426b7ad | |
![]() |
094c8fc48b | |
![]() |
e86e1e0430 | |
![]() |
34375c6cc6 | |
![]() |
b9527cfe9d | |
![]() |
5f2adb42ed | |
![]() |
7b177a7929 | |
![]() |
aa5d274464 | |
![]() |
fb65d1c581 | |
![]() |
c0ca7f8bea | |
![]() |
4c30c50490 | |
![]() |
903a9e57a8 | |
![]() |
830150b309 | |
![]() |
e10e241fe9 | |
![]() |
eeb480c051 | |
![]() |
ca9b90be9a | |
![]() |
e02704e05a | |
![]() |
5cc21dc3f0 | |
![]() |
806cd33d1f | |
![]() |
f8ca066d74 | |
![]() |
59b7e8e3ba | |
![]() |
571219f073 | |
![]() |
9084554f4f | |
![]() |
1b179c8e2a | |
![]() |
fb8f5b79ad | |
![]() |
e427b0ff75 | |
![]() |
b4e3c32e24 | |
![]() |
c511e324bb | |
![]() |
1eb0f7b3b2 | |
![]() |
7d003e0bfc | |
![]() |
927264d793 | |
![]() |
70da59c3bb | |
![]() |
c69e1c6222 | |
![]() |
6666210211 | |
![]() |
238eaa8e40 |
|
@ -42,3 +42,11 @@ erlang.mk
|
||||||
etc/emqx.conf.rendered
|
etc/emqx.conf.rendered
|
||||||
Mnesia.*/
|
Mnesia.*/
|
||||||
.stamp
|
.stamp
|
||||||
|
erlang_ls.config
|
||||||
|
# Emacs Backup files
|
||||||
|
*~
|
||||||
|
# Emacs temporary files
|
||||||
|
.#*
|
||||||
|
*#
|
||||||
|
# For direnv
|
||||||
|
.envrc
|
||||||
|
|
|
@ -202,9 +202,11 @@ mqtt.max_packet_size = 1MB
|
||||||
mqtt.max_clientid_len = 65535
|
mqtt.max_clientid_len = 65535
|
||||||
|
|
||||||
## Maximum topic levels allowed. 0 means no limit.
|
## Maximum topic levels allowed. 0 means no limit.
|
||||||
|
## Depth so big may lead to subscribing performance issues.
|
||||||
##
|
##
|
||||||
## Value: Number
|
## Value: Number [0-65535]
|
||||||
mqtt.max_topic_levels = 0
|
## Default: 128
|
||||||
|
mqtt.max_topic_levels = 128
|
||||||
|
|
||||||
## Maximum QoS allowed.
|
## Maximum QoS allowed.
|
||||||
##
|
##
|
||||||
|
@ -242,7 +244,7 @@ mqtt.ignore_loop_deliver = false
|
||||||
mqtt.strict_mode = false
|
mqtt.strict_mode = false
|
||||||
|
|
||||||
## Specify the response information returned to the client
|
## Specify the response information returned to the client
|
||||||
##
|
##
|
||||||
## Value: String
|
## Value: String
|
||||||
## mqtt.response_information = example
|
## mqtt.response_information = example
|
||||||
|
|
||||||
|
@ -296,6 +298,16 @@ broker.shared_dispatch_ack_enabled = false
|
||||||
## Value: Flag
|
## Value: Flag
|
||||||
broker.route_batch_clean = off
|
broker.route_batch_clean = off
|
||||||
|
|
||||||
|
## Performance toggle for subscribe/unsubscribe wildcard topic.
|
||||||
|
## Change this toggle only when there are many wildcard topics.
|
||||||
|
## Value: Enum
|
||||||
|
## - key: mnesia translational updates with per-key locks. recommended for single node setup.
|
||||||
|
## - tab: mnesia translational updates with table lock. recommended for multi-nodes setup.
|
||||||
|
## - global: global lock protected updates. recommended for larger cluster.
|
||||||
|
## NOTE: when changing from/to 'global' lock, it requires all nodes in the cluster
|
||||||
|
## to be stopped before the change.
|
||||||
|
broker.perf.route_lock_type = key
|
||||||
|
|
||||||
##-------------------------------------------------------------------
|
##-------------------------------------------------------------------
|
||||||
## Plugins
|
## Plugins
|
||||||
##-------------------------------------------------------------------
|
##-------------------------------------------------------------------
|
||||||
|
|
|
@ -544,6 +544,16 @@ listener.ws.external.verify_protocol_header = on
|
||||||
## Value: Duration
|
## Value: Duration
|
||||||
## listener.ws.external.proxy_protocol_timeout = 3s
|
## listener.ws.external.proxy_protocol_timeout = 3s
|
||||||
|
|
||||||
|
## See: listener.ssl.$name.peer_cert_as_username
|
||||||
|
##
|
||||||
|
## Value: cn
|
||||||
|
## listener.ws.external.peer_cert_as_username = cn
|
||||||
|
|
||||||
|
## See: listener.ssl.$name.peer_cert_as_clientid
|
||||||
|
##
|
||||||
|
## Value: cn
|
||||||
|
## listener.ws.external.peer_cert_as_clientid = cn
|
||||||
|
|
||||||
## The TCP backlog of external MQTT/WebSocket Listener.
|
## The TCP backlog of external MQTT/WebSocket Listener.
|
||||||
##
|
##
|
||||||
## See: listener.ws.$name.backlog
|
## See: listener.ws.$name.backlog
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
## - file: write logs only to file
|
## - file: write logs only to file
|
||||||
## - console: write logs only to standard I/O
|
## - console: write logs only to standard I/O
|
||||||
## - both: write logs both to file and standard I/O
|
## - both: write logs both to file and standard I/O
|
||||||
log.to = both
|
log.to = file
|
||||||
|
|
||||||
## The log severity level.
|
## The log severity level.
|
||||||
##
|
##
|
||||||
|
@ -167,4 +167,4 @@ log.rotation.count = 5
|
||||||
## Value: MaxBurstCount,TimeWindow
|
## Value: MaxBurstCount,TimeWindow
|
||||||
## Default: disabled
|
## Default: disabled
|
||||||
##
|
##
|
||||||
#log.burst_limit = 20000, 1s
|
#log.burst_limit = 20000, 1s
|
||||||
|
|
|
@ -57,8 +57,9 @@ zone.external.force_shutdown_policy = 10000|32MB
|
||||||
## zone.external.max_clientid_len = 1024
|
## zone.external.max_clientid_len = 1024
|
||||||
|
|
||||||
## Maximum topic levels allowed. 0 means no limit.
|
## Maximum topic levels allowed. 0 means no limit.
|
||||||
|
## Depth so big may lead to subscribing performance issues.
|
||||||
##
|
##
|
||||||
## Value: Number
|
## Value: Number [0-65535]
|
||||||
## zone.external.max_topic_levels = 7
|
## zone.external.max_topic_levels = 7
|
||||||
|
|
||||||
## Maximum QoS allowed.
|
## Maximum QoS allowed.
|
||||||
|
@ -184,6 +185,30 @@ zone.external.enable_flapping_detect = off
|
||||||
## Example: 100KB incoming per 10 seconds.
|
## Example: 100KB incoming per 10 seconds.
|
||||||
#zone.external.rate_limit.conn_bytes_in = 100KB,10s
|
#zone.external.rate_limit.conn_bytes_in = 100KB,10s
|
||||||
|
|
||||||
|
## Whether to alarm the congested connections.
|
||||||
|
##
|
||||||
|
## Sometimes the mqtt connection (usually an MQTT subscriber) may get "congested" because
|
||||||
|
## there're too many packets to sent. The socket trys to buffer the packets until the buffer is
|
||||||
|
## full. If more packets comes after that, the packets will be "pending" in a queue
|
||||||
|
## and we consider the connection is "congested".
|
||||||
|
##
|
||||||
|
## Enable this to send an alarm when there's any bytes pending in the queue. You could set
|
||||||
|
## the `listener.tcp.<ZoneName>.sndbuf` to a larger value if the alarm is triggered too often.
|
||||||
|
##
|
||||||
|
## The name of the alarm is of format "conn_congestion/<ClientID>/<Username>".
|
||||||
|
## Where the <ClientID> is the client-id of the congested MQTT connection.
|
||||||
|
## And the <Username> is the username or "unknown_user" of not provided by the client.
|
||||||
|
## Default: off
|
||||||
|
#zone.external.conn_congestion.alarm = off
|
||||||
|
|
||||||
|
## Won't clear the congested alarm in how long time.
|
||||||
|
## The alarm is cleared only when there're no pending bytes in the queue, and also it has been
|
||||||
|
## `min_alarm_sustain_duration` time since the last time we considered the connection is "congested".
|
||||||
|
##
|
||||||
|
## This is to avoid clearing and sending the alarm again too often.
|
||||||
|
## Default: 1m
|
||||||
|
#zone.external.conn_congestion.min_alarm_sustain_duration = 1m
|
||||||
|
|
||||||
## Messages quota for the each of external MQTT connection.
|
## Messages quota for the each of external MQTT connection.
|
||||||
## This value consumed by the number of recipient on a message.
|
## This value consumed by the number of recipient on a message.
|
||||||
##
|
##
|
||||||
|
@ -226,7 +251,7 @@ zone.external.ignore_loop_deliver = false
|
||||||
zone.external.strict_mode = false
|
zone.external.strict_mode = false
|
||||||
|
|
||||||
## Specify the response information returned to the client
|
## Specify the response information returned to the client
|
||||||
##
|
##
|
||||||
## Value: String
|
## Value: String
|
||||||
## zone.external.response_information = example
|
## zone.external.response_information = example
|
||||||
|
|
||||||
|
@ -317,7 +342,7 @@ zone.internal.ignore_loop_deliver = false
|
||||||
zone.internal.strict_mode = false
|
zone.internal.strict_mode = false
|
||||||
|
|
||||||
## Specify the response information returned to the client
|
## Specify the response information returned to the client
|
||||||
##
|
##
|
||||||
## Value: String
|
## Value: String
|
||||||
## zone.internal.response_information = example
|
## zone.internal.response_information = example
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,13 @@
|
||||||
%% MQTT Protocol Version and Names
|
%% MQTT Protocol Version and Names
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(MQTT_SN_PROTO_V1, 1).
|
||||||
-define(MQTT_PROTO_V3, 3).
|
-define(MQTT_PROTO_V3, 3).
|
||||||
-define(MQTT_PROTO_V4, 4).
|
-define(MQTT_PROTO_V4, 4).
|
||||||
-define(MQTT_PROTO_V5, 5).
|
-define(MQTT_PROTO_V5, 5).
|
||||||
|
|
||||||
-define(PROTOCOL_NAMES, [
|
-define(PROTOCOL_NAMES, [
|
||||||
|
{?MQTT_SN_PROTO_V1, <<"MQTT-SN">>}, %% XXX:Compatible with emqx-sn plug-in
|
||||||
{?MQTT_PROTO_V3, <<"MQIsdp">>},
|
{?MQTT_PROTO_V3, <<"MQIsdp">>},
|
||||||
{?MQTT_PROTO_V4, <<"MQTT">>},
|
{?MQTT_PROTO_V4, <<"MQTT">>},
|
||||||
{?MQTT_PROTO_V5, <<"MQTT">>}]).
|
{?MQTT_PROTO_V5, <<"MQTT">>}]).
|
||||||
|
|
|
@ -757,7 +757,7 @@ end}.
|
||||||
|
|
||||||
%% @doc Set the Maximum topic levels.
|
%% @doc Set the Maximum topic levels.
|
||||||
{mapping, "mqtt.max_topic_levels", "emqx.max_topic_levels", [
|
{mapping, "mqtt.max_topic_levels", "emqx.max_topic_levels", [
|
||||||
{default, 0},
|
{default, 128},
|
||||||
{datatype, integer}
|
{datatype, integer}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
@ -999,6 +999,16 @@ end}.
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{mapping, "zone.$name.conn_congestion.alarm", "emqx.zones", [
|
||||||
|
{datatype, flag},
|
||||||
|
{default, off}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "zone.$name.conn_congestion.min_alarm_sustain_duration", "emqx.zones", [
|
||||||
|
{default, "1m"},
|
||||||
|
{datatype, {duration, ms}}
|
||||||
|
]}.
|
||||||
|
|
||||||
{mapping, "zone.$name.quota.conn_messages_routing", "emqx.zones", [
|
{mapping, "zone.$name.quota.conn_messages_routing", "emqx.zones", [
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
@ -1128,6 +1138,10 @@ end}.
|
||||||
{ratelimit, {conn_messages_in, Ratelimit(Val)}};
|
{ratelimit, {conn_messages_in, Ratelimit(Val)}};
|
||||||
(["rate_limit", "conn_bytes_in"], Val) ->
|
(["rate_limit", "conn_bytes_in"], Val) ->
|
||||||
{ratelimit, {conn_bytes_in, Ratelimit(Val)}};
|
{ratelimit, {conn_bytes_in, Ratelimit(Val)}};
|
||||||
|
(["conn_congestion", "alarm"], Val) ->
|
||||||
|
{conn_congestion_alarm_enabled, Val};
|
||||||
|
(["conn_congestion", "min_alarm_sustain_duration"], Val) ->
|
||||||
|
{conn_congestion_min_alarm_sustain_duration, Val};
|
||||||
(["quota", "conn_messages_routing"], Val) ->
|
(["quota", "conn_messages_routing"], Val) ->
|
||||||
{quota, {conn_messages_routing, Ratelimit(Val)}};
|
{quota, {conn_messages_routing, Ratelimit(Val)}};
|
||||||
(["quota", "overall_messages_routing"], Val) ->
|
(["quota", "overall_messages_routing"], Val) ->
|
||||||
|
@ -1573,7 +1587,11 @@ end}.
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.ws.$name.peer_cert_as_username", "emqx.listeners", [
|
{mapping, "listener.ws.$name.peer_cert_as_username", "emqx.listeners", [
|
||||||
{datatype, {enum, [cn, dn, crt]}}
|
{datatype, {enum, [cn]}}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "listener.ws.$name.peer_cert_as_clientid", "emqx.listeners", [
|
||||||
|
{datatype, {enum, [cn]}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -2008,6 +2026,18 @@ end}.
|
||||||
{datatype, flag}
|
{datatype, flag}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
%% @doc Performance toggle for subscribe/unsubscribe wildcard topic.
|
||||||
|
%% Change this toggle only when there are many wildcard topics.
|
||||||
|
%% key: mnesia translational updates with per-key locks. recommended for single node setup.
|
||||||
|
%% tab: mnesia translational updates with table lock. recommended for multi-nodes setup.
|
||||||
|
%% global: global lock protected updates. recommended for larger cluster.
|
||||||
|
%% NOTE: when changing from/to 'global' lock, it requires all nodes in the cluster
|
||||||
|
%%
|
||||||
|
{mapping, "broker.perf.route_lock_type", "emqx.route_lock_type", [
|
||||||
|
{default, key},
|
||||||
|
{datatype, {enum, [key, tab, global]}}
|
||||||
|
]}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% System Monitor
|
%% System Monitor
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
[{gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}},
|
[{gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}},
|
||||||
{jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}},
|
{jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}},
|
||||||
{cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.7.1"}}},
|
{cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.7.1"}}},
|
||||||
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.7.5"}}},
|
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.7.6"}}},
|
||||||
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.7.5"}}},
|
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.7.10"}}},
|
||||||
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.0"}}},
|
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.0"}}},
|
||||||
{cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}
|
{cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
%% -*-: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
|
|
||||||
{VSN,
|
{VSN,
|
||||||
[
|
[
|
||||||
|
@ -6,6 +6,7 @@
|
||||||
{add_module, emqx_congestion},
|
{add_module, emqx_congestion},
|
||||||
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_metrics, brutal_purge, soft_purge, []},
|
{load_module, emqx_metrics, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
||||||
{suspend, [esockd_acceptor,emqx_connection, emqx_ws_connection]},
|
{suspend, [esockd_acceptor,emqx_connection, emqx_ws_connection]},
|
||||||
|
@ -14,12 +15,24 @@
|
||||||
{update, emqx_ws_connection, {advanced, []}},
|
{update, emqx_ws_connection, {advanced, []}},
|
||||||
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
||||||
{resume, [esockd_acceptor,emqx_connection, emqx_ws_connection]}
|
{resume, [esockd_acceptor,emqx_connection, emqx_ws_connection]},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, [emqx_trie]},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]}
|
||||||
]},
|
]},
|
||||||
{"4.2.1", [
|
{"4.2.1", [
|
||||||
{add_module, emqx_congestion},
|
{add_module, emqx_congestion},
|
||||||
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
||||||
{suspend, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
{suspend, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
||||||
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
@ -27,15 +40,129 @@
|
||||||
{update, emqx_ws_connection, {advanced, []}},
|
{update, emqx_ws_connection, {advanced, []}},
|
||||||
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
||||||
{resume, [esockd_acceptor, emqx_connection, emqx_ws_connection]}
|
{resume, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, [emqx_trie]},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]}
|
||||||
]},
|
]},
|
||||||
{<<"4.2.[23]">>, [
|
{<<"4.2.[23]">>, [
|
||||||
{add_module, emqx_congestion},
|
{add_module, emqx_congestion},
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
|
{update, emqx_connection, {advanced, []}},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []}
|
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, [emqx_trie]},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.4">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_congestion, brutal_purge, soft_purge, []},
|
||||||
|
{update, emqx_connection, {advanced, []}},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, [emqx_trie]},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.5">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_congestion, brutal_purge, soft_purge, []},
|
||||||
|
{update, emqx_connection, {advanced, []}},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, [emqx_trie]},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.[6-7]">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.8">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.9">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.10">>, [
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []}
|
||||||
]},
|
]},
|
||||||
{<<".*">>, []}
|
{<<".*">>, []}
|
||||||
],
|
],
|
||||||
|
@ -51,8 +178,20 @@
|
||||||
{update, emqx_connection, {advanced, []}},
|
{update, emqx_connection, {advanced, []}},
|
||||||
{update, emqx_ws_connection, {advanced, []}},
|
{update, emqx_ws_connection, {advanced, []}},
|
||||||
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
{resume, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
{resume, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
||||||
{delete_module, emqx_congestion}
|
{delete_module, emqx_congestion},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, [emqx_router]},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]}
|
||||||
]},
|
]},
|
||||||
{"4.2.1", [
|
{"4.2.1", [
|
||||||
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
||||||
|
@ -60,20 +199,135 @@
|
||||||
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
||||||
{suspend, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
{suspend, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
||||||
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
{update, emqx_connection, {advanced, []}},
|
{update, emqx_connection, {advanced, []}},
|
||||||
{update, emqx_ws_connection, {advanced, []}},
|
{update, emqx_ws_connection, {advanced, []}},
|
||||||
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
{resume, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
{resume, [esockd_acceptor, emqx_connection, emqx_ws_connection]},
|
||||||
{delete_module, emqx_congestion}
|
{delete_module, emqx_congestion},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, [emqx_router]},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]}
|
||||||
]},
|
]},
|
||||||
{<<"4.2.[23]">>, [
|
{<<"4.2.[23]">>, [
|
||||||
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
{load_module, emqx_shared_sub, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
{update, emqx_connection, {advanced, []}},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
{delete_module, emqx_congestion}
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{delete_module, emqx_congestion},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, [emqx_router]},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.4">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_congestion, brutal_purge, soft_purge, []},
|
||||||
|
{update, emqx_connection, {advanced, []}},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, [emqx_router]},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.5">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_session, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_congestion, brutal_purge, soft_purge, []},
|
||||||
|
{update, emqx_connection, {advanced, []}},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_router_sup, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_broker, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_trie, soft_purge, soft_purge, [emqx_router]},
|
||||||
|
{load_module, emqx_router, soft_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.[6-7]">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_access_rule, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.8">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_cm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.9">>, [
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_ws_connection, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_limiter, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<"4.2.10">>, [
|
||||||
|
{load_module, emqx_channel, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_frame, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_os_mon, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_alarm, brutal_purge, soft_purge, []}
|
||||||
]},
|
]},
|
||||||
{<<".*">>, []}
|
{<<".*">>, []}
|
||||||
]
|
]
|
||||||
|
|
|
@ -28,7 +28,8 @@
|
||||||
-type(who() :: all | binary() |
|
-type(who() :: all | binary() |
|
||||||
{client, binary()} |
|
{client, binary()} |
|
||||||
{user, binary()} |
|
{user, binary()} |
|
||||||
{ipaddr, esockd_cidr:cidr_string()}).
|
{ipaddr, esockd_cidr:cidr_string()} |
|
||||||
|
{ipaddrs, list(esockd_cidr:cidr_string())}).
|
||||||
|
|
||||||
-type(access() :: subscribe | publish | pubsub).
|
-type(access() :: subscribe | publish | pubsub).
|
||||||
|
|
||||||
|
@ -52,6 +53,8 @@ compile(who, all) ->
|
||||||
all;
|
all;
|
||||||
compile(who, {ipaddr, CIDR}) ->
|
compile(who, {ipaddr, CIDR}) ->
|
||||||
{ipaddr, esockd_cidr:parse(CIDR, true)};
|
{ipaddr, esockd_cidr:parse(CIDR, true)};
|
||||||
|
compile(who, {ipaddrs, CIDRs}) ->
|
||||||
|
{ipaddrs, lists:map(fun(CIDR) -> esockd_cidr:parse(CIDR, true) end, CIDRs)};
|
||||||
compile(who, {client, all}) ->
|
compile(who, {client, all}) ->
|
||||||
{client, all};
|
{client, all};
|
||||||
compile(who, {client, ClientId}) ->
|
compile(who, {client, ClientId}) ->
|
||||||
|
@ -108,8 +111,14 @@ match_who(#{username := Username}, {user, Username}) ->
|
||||||
true;
|
true;
|
||||||
match_who(#{peerhost := undefined}, {ipaddr, _Tup}) ->
|
match_who(#{peerhost := undefined}, {ipaddr, _Tup}) ->
|
||||||
false;
|
false;
|
||||||
|
match_who(#{peerhost := undefined}, {ipaddrs, _}) ->
|
||||||
|
false;
|
||||||
match_who(#{peerhost := IP}, {ipaddr, CIDR}) ->
|
match_who(#{peerhost := IP}, {ipaddr, CIDR}) ->
|
||||||
esockd_cidr:match(IP, CIDR);
|
esockd_cidr:match(IP, CIDR);
|
||||||
|
match_who(#{peerhost := IP}, {ipaddrs, CIDRs}) ->
|
||||||
|
lists:any(fun(CIDR) ->
|
||||||
|
esockd_cidr:match(IP, CIDR)
|
||||||
|
end, CIDRs);
|
||||||
match_who(ClientInfo, {'and', Conds}) when is_list(Conds) ->
|
match_who(ClientInfo, {'and', Conds}) when is_list(Conds) ->
|
||||||
lists:foldl(fun(Who, Allow) ->
|
lists:foldl(fun(Who, Allow) ->
|
||||||
match_who(ClientInfo, Who) andalso Allow
|
match_who(ClientInfo, Who) andalso Allow
|
||||||
|
|
|
@ -56,9 +56,9 @@
|
||||||
name :: binary() | atom(),
|
name :: binary() | atom(),
|
||||||
|
|
||||||
details :: map() | list(),
|
details :: map() | list(),
|
||||||
|
|
||||||
message :: binary(),
|
message :: binary(),
|
||||||
|
|
||||||
activate_at :: integer()
|
activate_at :: integer()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -68,9 +68,9 @@
|
||||||
name :: binary() | atom(),
|
name :: binary() | atom(),
|
||||||
|
|
||||||
details :: map() | list(),
|
details :: map() | list(),
|
||||||
|
|
||||||
message :: binary(),
|
message :: binary(),
|
||||||
|
|
||||||
deactivate_at :: integer() | infinity
|
deactivate_at :: integer() | infinity
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -165,6 +165,8 @@ init([Opts]) ->
|
||||||
Actions = proplists:get_value(actions, Opts),
|
Actions = proplists:get_value(actions, Opts),
|
||||||
SizeLimit = proplists:get_value(size_limit, Opts),
|
SizeLimit = proplists:get_value(size_limit, Opts),
|
||||||
ValidityPeriod = timer:seconds(proplists:get_value(validity_period, Opts)),
|
ValidityPeriod = timer:seconds(proplists:get_value(validity_period, Opts)),
|
||||||
|
emqx_alarm_handler:load(),
|
||||||
|
process_flag(trap_exit, true),
|
||||||
{ok, ensure_delete_timer(#state{actions = Actions,
|
{ok, ensure_delete_timer(#state{actions = Actions,
|
||||||
size_limit = SizeLimit,
|
size_limit = SizeLimit,
|
||||||
validity_period = ValidityPeriod})}.
|
validity_period = ValidityPeriod})}.
|
||||||
|
@ -228,6 +230,7 @@ handle_info(Info, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
emqx_alarm_handler:unload(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
@ -361,7 +364,7 @@ normalize_message(partition, #{occurred := Node}) ->
|
||||||
list_to_binary(io_lib:format("Partition occurs at node ~s", [Node]));
|
list_to_binary(io_lib:format("Partition occurs at node ~s", [Node]));
|
||||||
normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) ->
|
normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) ->
|
||||||
list_to_binary(io_lib:format("Resource ~s(~s) is down", [Type, ID]));
|
list_to_binary(io_lib:format("Resource ~s(~s) is down", [Type, ID]));
|
||||||
normalize_message(<<"mqtt_conn/congested/", Info/binary>>, _) ->
|
normalize_message(<<"conn_congestion/", Info/binary>>, _) ->
|
||||||
list_to_binary(io_lib:format("MQTT connection congested: ~s", [Info]));
|
list_to_binary(io_lib:format("connection congested: ~s", [Info]));
|
||||||
normalize_message(_Name, _UnknownDetails) ->
|
normalize_message(_Name, _UnknownDetails) ->
|
||||||
<<"Unknown alarm">>.
|
<<"Unknown alarm">>.
|
||||||
|
|
|
@ -56,20 +56,20 @@ init({_Args, {alarm_handler, _ExistingAlarms}}) ->
|
||||||
init(_) ->
|
init(_) ->
|
||||||
{ok, []}.
|
{ok, []}.
|
||||||
|
|
||||||
handle_event({set_alarm, {system_memory_high_watermark, []}}, State) ->
|
handle_event({set_alarm, {system_memory_high_watermark, []}}, State) ->
|
||||||
emqx_alarm:activate(high_system_memory_usage, #{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}),
|
emqx_alarm:activate(high_system_memory_usage, #{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}),
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
|
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
|
||||||
emqx_alarm:activate(high_process_memory_usage, #{pid => Pid,
|
emqx_alarm:activate(high_process_memory_usage, #{pid => list_to_binary(pid_to_list(Pid)),
|
||||||
high_watermark => emqx_os_mon:get_procmem_high_watermark()}),
|
high_watermark => emqx_os_mon:get_procmem_high_watermark()}),
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
handle_event({clear_alarm, system_memory_high_watermark}, State) ->
|
handle_event({clear_alarm, system_memory_high_watermark}, State) ->
|
||||||
emqx_alarm:deactivate(high_system_memory_usage),
|
emqx_alarm:deactivate(high_system_memory_usage),
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
handle_event({clear_alarm, process_memory_high_watermark}, State) ->
|
handle_event({clear_alarm, process_memory_high_watermark}, State) ->
|
||||||
emqx_alarm:deactivate(high_process_memory_usage),
|
emqx_alarm:deactivate(high_process_memory_usage),
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
|
@ -85,4 +85,4 @@ handle_call(_Query, State) ->
|
||||||
terminate(swap, _State) ->
|
terminate(swap, _State) ->
|
||||||
{emqx_alarm_handler, []};
|
{emqx_alarm_handler, []};
|
||||||
terminate(_, _) ->
|
terminate(_, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -38,13 +38,11 @@ start(_Type, _Args) ->
|
||||||
ok = emqx_plugins:init(),
|
ok = emqx_plugins:init(),
|
||||||
emqx_plugins:load(),
|
emqx_plugins:load(),
|
||||||
register(emqx, self()),
|
register(emqx, self()),
|
||||||
emqx_alarm_handler:load(),
|
|
||||||
print_vsn(),
|
print_vsn(),
|
||||||
{ok, Sup}.
|
{ok, Sup}.
|
||||||
|
|
||||||
-spec(stop(State :: term()) -> term()).
|
-spec(stop(State :: term()) -> term()).
|
||||||
stop(_State) ->
|
stop(_State) ->
|
||||||
emqx_alarm_handler:unload(),
|
|
||||||
emqx_boot:is_enabled(listeners)
|
emqx_boot:is_enabled(listeners)
|
||||||
andalso emqx_listeners:stop().
|
andalso emqx_listeners:stop().
|
||||||
|
|
||||||
|
@ -68,4 +66,3 @@ start_autocluster() ->
|
||||||
ekka:callback(prepare, fun emqx:shutdown/1),
|
ekka:callback(prepare, fun emqx:shutdown/1),
|
||||||
ekka:callback(reboot, fun emqx:reboot/0),
|
ekka:callback(reboot, fun emqx:reboot/0),
|
||||||
ekka:autocluster(?APP).
|
ekka:autocluster(?APP).
|
||||||
|
|
||||||
|
|
|
@ -419,7 +419,7 @@ safe_update_stats(Tab, Stat, MaxStat) ->
|
||||||
-compile({inline, [call/2, cast/2, pick/1]}).
|
-compile({inline, [call/2, cast/2, pick/1]}).
|
||||||
|
|
||||||
call(Broker, Req) ->
|
call(Broker, Req) ->
|
||||||
gen_server:call(Broker, Req).
|
gen_server:call(Broker, Req, infinity).
|
||||||
|
|
||||||
cast(Broker, Msg) ->
|
cast(Broker, Msg) ->
|
||||||
gen_server:cast(Broker, Msg).
|
gen_server:cast(Broker, Msg).
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
-export([ info/1
|
-export([ info/1
|
||||||
, info/2
|
, info/2
|
||||||
, set_conn_state/2
|
, set_conn_state/2
|
||||||
|
, get_session/1
|
||||||
|
, set_session/2
|
||||||
, stats/1
|
, stats/1
|
||||||
, caps/1
|
, caps/1
|
||||||
]).
|
]).
|
||||||
|
@ -46,6 +48,12 @@
|
||||||
, terminate/2
|
, terminate/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
%% Export for emqx_sn
|
||||||
|
-export([ do_deliver/2
|
||||||
|
, ensure_keepalive/2
|
||||||
|
, clear_keepalive/1
|
||||||
|
]).
|
||||||
|
|
||||||
%% Exports for CT
|
%% Exports for CT
|
||||||
-export([set_field/3]).
|
-export([set_field/3]).
|
||||||
|
|
||||||
|
@ -167,6 +175,12 @@ info(timers, #channel{timers = Timers}) -> Timers.
|
||||||
set_conn_state(ConnState, Channel) ->
|
set_conn_state(ConnState, Channel) ->
|
||||||
Channel#channel{conn_state = ConnState}.
|
Channel#channel{conn_state = ConnState}.
|
||||||
|
|
||||||
|
get_session(#channel{session = Session}) ->
|
||||||
|
Session.
|
||||||
|
|
||||||
|
set_session(Session, Channel) ->
|
||||||
|
Channel#channel{session = Session}.
|
||||||
|
|
||||||
%% TODO: Add more stats.
|
%% TODO: Add more stats.
|
||||||
-spec(stats(channel()) -> emqx_types:stats()).
|
-spec(stats(channel()) -> emqx_types:stats()).
|
||||||
stats(#channel{session = Session})->
|
stats(#channel{session = Session})->
|
||||||
|
@ -931,8 +945,11 @@ handle_info({sock_closed, Reason}, Channel =
|
||||||
Shutdown -> Shutdown
|
Shutdown -> Shutdown
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({sock_closed, Reason}, Channel = #channel{conn_state = disconnected}) ->
|
handle_info({sock_closed, _Reason}, Channel = #channel{conn_state = disconnected}) ->
|
||||||
?LOG(error, "Unexpected sock_closed: ~p", [Reason]),
|
%% Since sock_closed messages can be generated multiple times,
|
||||||
|
%% we can simply ignore errors of this type in the disconnected state.
|
||||||
|
%% e.g. when the socket send function returns an error, there is already
|
||||||
|
%% a tcp_closed delivered to the process mailbox
|
||||||
{ok, Channel};
|
{ok, Channel};
|
||||||
|
|
||||||
handle_info(clean_acl_cache, Channel) ->
|
handle_info(clean_acl_cache, Channel) ->
|
||||||
|
@ -1438,11 +1455,13 @@ enrich_connack_caps(AckProps, _Channel) -> AckProps.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Enrich server keepalive
|
%% Enrich server keepalive
|
||||||
|
|
||||||
enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) ->
|
enrich_server_keepalive(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{zone := Zone}}) ->
|
||||||
case emqx_zone:server_keepalive(Zone) of
|
case emqx_zone:server_keepalive(Zone) of
|
||||||
undefined -> AckProps;
|
undefined -> AckProps;
|
||||||
Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive}
|
Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive}
|
||||||
end.
|
end;
|
||||||
|
|
||||||
|
enrich_server_keepalive(AckProps, _Channel) -> AckProps.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Enrich response information
|
%% Enrich response information
|
||||||
|
@ -1488,7 +1507,7 @@ init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
|
||||||
init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined.
|
init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Enrich Keepalive
|
%% Ensure Keepalive
|
||||||
|
|
||||||
ensure_keepalive(#{'Server-Keep-Alive' := Interval}, Channel) ->
|
ensure_keepalive(#{'Server-Keep-Alive' := Interval}, Channel) ->
|
||||||
ensure_keepalive_timer(Interval, Channel);
|
ensure_keepalive_timer(Interval, Channel);
|
||||||
|
@ -1501,6 +1520,15 @@ ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}
|
||||||
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
|
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
|
||||||
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
|
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
|
||||||
|
|
||||||
|
clear_keepalive(Channel = #channel{timers = Timers}) ->
|
||||||
|
case maps:get(alive_timer, Timers, undefined) of
|
||||||
|
undefined ->
|
||||||
|
Channel;
|
||||||
|
TRef ->
|
||||||
|
emqx_misc:cancel_timer(TRef),
|
||||||
|
Channel#channel{timers = maps:without([alive_timer], Timers)}
|
||||||
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Maybe Resume Session
|
%% Maybe Resume Session
|
||||||
|
|
||||||
|
@ -1645,4 +1673,3 @@ flag(false) -> 0.
|
||||||
set_field(Name, Value, Channel) ->
|
set_field(Name, Value, Channel) ->
|
||||||
Pos = emqx_misc:index_of(Name, record_info(fields, channel)),
|
Pos = emqx_misc:index_of(Name, record_info(fields, channel)),
|
||||||
setelement(Pos+1, Channel, Value).
|
setelement(Pos+1, Channel, Value).
|
||||||
|
|
||||||
|
|
163
src/emqx_cm.erl
163
src/emqx_cm.erl
|
@ -70,7 +70,10 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Internal export
|
%% Internal export
|
||||||
-export([stats_fun/0]).
|
-export([stats_fun/0, clean_down/1]).
|
||||||
|
|
||||||
|
%% Test export
|
||||||
|
-export([register_channel_/3]).
|
||||||
|
|
||||||
-type(chan_pid() :: pid()).
|
-type(chan_pid() :: pid()).
|
||||||
|
|
||||||
|
@ -91,6 +94,10 @@
|
||||||
%% Server name
|
%% Server name
|
||||||
-define(CM, ?MODULE).
|
-define(CM, ?MODULE).
|
||||||
|
|
||||||
|
-define(T_KICK, 5000).
|
||||||
|
-define(T_GET_INFO, 5000).
|
||||||
|
-define(T_TAKEOVER, 15000).
|
||||||
|
|
||||||
%% @doc Start the channel manager.
|
%% @doc Start the channel manager.
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
start_link() ->
|
start_link() ->
|
||||||
|
@ -159,7 +166,7 @@ get_chan_info(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
error:badarg -> undefined
|
error:badarg -> undefined
|
||||||
end;
|
end;
|
||||||
get_chan_info(ClientId, ChanPid) ->
|
get_chan_info(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), get_chan_info, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), get_chan_info, [ClientId, ChanPid], ?T_GET_INFO).
|
||||||
|
|
||||||
%% @doc Update infos of the channel.
|
%% @doc Update infos of the channel.
|
||||||
-spec(set_chan_info(emqx_types:clientid(), emqx_types:attrs()) -> boolean()).
|
-spec(set_chan_info(emqx_types:clientid(), emqx_types:attrs()) -> boolean()).
|
||||||
|
@ -184,7 +191,7 @@ get_chan_stats(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
error:badarg -> undefined
|
error:badarg -> undefined
|
||||||
end;
|
end;
|
||||||
get_chan_stats(ClientId, ChanPid) ->
|
get_chan_stats(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid], ?T_GET_INFO).
|
||||||
|
|
||||||
%% @doc Set channel's stats.
|
%% @doc Set channel's stats.
|
||||||
-spec(set_chan_stats(emqx_types:clientid(), emqx_types:stats()) -> boolean()).
|
-spec(set_chan_stats(emqx_types:clientid(), emqx_types:stats()) -> boolean()).
|
||||||
|
@ -222,7 +229,7 @@ open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
|
||||||
case takeover_session(ClientId) of
|
case takeover_session(ClientId) of
|
||||||
{ok, ConnMod, ChanPid, Session} ->
|
{ok, ConnMod, ChanPid, Session} ->
|
||||||
ok = emqx_session:resume(ClientInfo, Session),
|
ok = emqx_session:resume(ClientInfo, Session),
|
||||||
Pendings = ConnMod:call(ChanPid, {takeover, 'end'}),
|
Pendings = ConnMod:call(ChanPid, {takeover, 'end'}, ?T_TAKEOVER),
|
||||||
register_channel_(ClientId, Self, ConnInfo),
|
register_channel_(ClientId, Self, ConnInfo),
|
||||||
{ok, #{session => Session,
|
{ok, #{session => Session,
|
||||||
present => true,
|
present => true,
|
||||||
|
@ -252,7 +259,7 @@ takeover_session(ClientId) ->
|
||||||
takeover_session(ClientId, ChanPid);
|
takeover_session(ClientId, ChanPid);
|
||||||
ChanPids ->
|
ChanPids ->
|
||||||
[ChanPid|StalePids] = lists:reverse(ChanPids),
|
[ChanPid|StalePids] = lists:reverse(ChanPids),
|
||||||
?LOG(error, "More than one channel found: ~p", [ChanPids]),
|
?LOG(error, "more_than_one_channel_found: ~p", [ChanPids]),
|
||||||
lists:foreach(fun(StalePid) ->
|
lists:foreach(fun(StalePid) ->
|
||||||
catch discard_session(ClientId, StalePid)
|
catch discard_session(ClientId, StalePid)
|
||||||
end, StalePids),
|
end, StalePids),
|
||||||
|
@ -264,67 +271,111 @@ takeover_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
undefined ->
|
undefined ->
|
||||||
{error, not_found};
|
{error, not_found};
|
||||||
ConnMod when is_atom(ConnMod) ->
|
ConnMod when is_atom(ConnMod) ->
|
||||||
Session = ConnMod:call(ChanPid, {takeover, 'begin'}),
|
%% TODO: if takeover times out, maybe kill the old?
|
||||||
|
Session = ConnMod:call(ChanPid, {takeover, 'begin'}, ?T_TAKEOVER),
|
||||||
{ok, ConnMod, ChanPid, Session}
|
{ok, ConnMod, ChanPid, Session}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
takeover_session(ClientId, ChanPid) ->
|
takeover_session(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid], ?T_TAKEOVER).
|
||||||
|
|
||||||
%% @doc Discard all the sessions identified by the ClientId.
|
%% @doc Discard all the sessions identified by the ClientId.
|
||||||
-spec(discard_session(emqx_types:clientid()) -> ok).
|
-spec(discard_session(emqx_types:clientid()) -> ok).
|
||||||
discard_session(ClientId) when is_binary(ClientId) ->
|
discard_session(ClientId) when is_binary(ClientId) ->
|
||||||
case lookup_channels(ClientId) of
|
case lookup_channels(ClientId) of
|
||||||
[] -> ok;
|
[] -> ok;
|
||||||
ChanPids ->
|
ChanPids -> lists:foreach(fun(Pid) -> discard_session(ClientId, Pid) end, ChanPids)
|
||||||
lists:foreach(
|
|
||||||
fun(ChanPid) ->
|
|
||||||
try
|
|
||||||
discard_session(ClientId, ChanPid)
|
|
||||||
catch
|
|
||||||
_:{noproc,_}:_Stk -> ok;
|
|
||||||
_:{{shutdown,_},_}:_Stk -> ok;
|
|
||||||
_:Error:_Stk ->
|
|
||||||
?LOG(error, "Failed to discard ~0p: ~0p", [ChanPid, Error])
|
|
||||||
end
|
|
||||||
end, ChanPids)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
discard_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
%% @private Kick a local stale session to force it step down.
|
||||||
case get_chann_conn_mod(ClientId, ChanPid) of
|
%% If failed to kick (e.g. timeout) force a kill.
|
||||||
undefined -> ok;
|
%% Keeping the stale pid around, or returning error or raise an exception
|
||||||
ConnMod when is_atom(ConnMod) ->
|
%% benefits nobody.
|
||||||
ConnMod:call(ChanPid, discard)
|
-spec kick_or_kill(kick | discard, module(), pid()) -> ok.
|
||||||
end;
|
kick_or_kill(Action, ConnMod, Pid) ->
|
||||||
|
try
|
||||||
|
%% this is essentailly a gen_server:call implemented in emqx_connection
|
||||||
|
%% and emqx_ws_connection.
|
||||||
|
%% the handle_call is implemented in emqx_channel
|
||||||
|
ok = apply(ConnMod, call, [Pid, Action, ?T_KICK])
|
||||||
|
catch
|
||||||
|
_ : noproc -> % emqx_ws_connection: call
|
||||||
|
?LOG(debug, "session_already_gone: ~p, action: ~p", [Pid, Action]),
|
||||||
|
ok;
|
||||||
|
_ : {noproc, _} -> % emqx_connection: gen_server:call
|
||||||
|
?LOG(debug, "session_already_gone: ~p, action: ~p", [Pid, Action]),
|
||||||
|
ok;
|
||||||
|
_ : {shutdown, _} ->
|
||||||
|
?LOG(debug, "session_already_shutdown: ~p, action: ~p", [Pid, Action]),
|
||||||
|
ok;
|
||||||
|
_ : {{shutdown, _}, _} ->
|
||||||
|
?LOG(debug, "session_already_shutdown: ~p, action: ~p", [Pid, Action]),
|
||||||
|
ok;
|
||||||
|
_ : {timeout, {gen_server, call, _}} ->
|
||||||
|
?LOG(warning, "session_kick_timeout: ~p, action: ~p, "
|
||||||
|
"stale_channel: ~p",
|
||||||
|
[Pid, Action, stale_channel_info(Pid)]),
|
||||||
|
ok = force_kill(Pid);
|
||||||
|
_ : Error ->
|
||||||
|
?LOG(error, "session_kick_exception: ~p, action: ~p, "
|
||||||
|
"reason: ~p, stacktrace: ~p, stale_channel: ~p",
|
||||||
|
[Pid, Action, Error, erlang:get_stacktrace(), stale_channel_info(Pid)]),
|
||||||
|
ok = force_kill(Pid)
|
||||||
|
end.
|
||||||
|
|
||||||
|
force_kill(Pid) ->
|
||||||
|
exit(Pid, kill),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
stale_channel_info(Pid) ->
|
||||||
|
process_info(Pid, [status, message_queue_len, current_stacktrace]).
|
||||||
|
|
||||||
discard_session(ClientId, ChanPid) ->
|
discard_session(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), discard_session, [ClientId, ChanPid]).
|
kick_session(discard, ClientId, ChanPid).
|
||||||
|
|
||||||
|
kick_session(ClientId, ChanPid) ->
|
||||||
|
kick_session(kick, ClientId, ChanPid).
|
||||||
|
|
||||||
|
%% @private This function is shared for session 'kick' and 'discard' (as the first arg Action).
|
||||||
|
kick_session(Action, ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
|
case get_chann_conn_mod(ClientId, ChanPid) of
|
||||||
|
undefined ->
|
||||||
|
%% already deregistered
|
||||||
|
ok;
|
||||||
|
ConnMod when is_atom(ConnMod) ->
|
||||||
|
ok = kick_or_kill(Action, ConnMod, ChanPid)
|
||||||
|
end;
|
||||||
|
kick_session(Action, ClientId, ChanPid) ->
|
||||||
|
%% call remote node on the old APIs because we do not know if they have upgraded
|
||||||
|
%% to have kick_session/3
|
||||||
|
Function = case Action of
|
||||||
|
discard -> discard_session;
|
||||||
|
kick -> kick_session
|
||||||
|
end,
|
||||||
|
try
|
||||||
|
rpc_call(node(ChanPid), Function, [ClientId, ChanPid], ?T_KICK)
|
||||||
|
catch
|
||||||
|
Error : Reason ->
|
||||||
|
%% This should mostly be RPC failures.
|
||||||
|
%% However, if the node is still running the old version
|
||||||
|
%% code (prior to emqx app 4.3.10) some of the RPC handler
|
||||||
|
%% exceptions may get propagated to a new version node
|
||||||
|
?LOG(error, "failed_to_kick_session_on_remote_node ~p: ~p ~p ~p",
|
||||||
|
[node(ChanPid), Action, Error, Reason])
|
||||||
|
end.
|
||||||
|
|
||||||
kick_session(ClientId) ->
|
kick_session(ClientId) ->
|
||||||
case lookup_channels(ClientId) of
|
case lookup_channels(ClientId) of
|
||||||
[] -> {error, not_found};
|
[] ->
|
||||||
[ChanPid] ->
|
?LOG(warning, "kiecked_an_unknown_session ~ts", [ClientId]),
|
||||||
kick_session(ClientId, ChanPid);
|
ok;
|
||||||
ChanPids ->
|
ChanPids ->
|
||||||
[ChanPid|StalePids] = lists:reverse(ChanPids),
|
case length(ChanPids) > 1 of
|
||||||
?LOG(error, "More than one channel found: ~p", [ChanPids]),
|
true -> ?LOG(info, "more_than_one_channel_found: ~p", [ChanPids]);
|
||||||
lists:foreach(fun(StalePid) ->
|
false -> ok
|
||||||
catch discard_session(ClientId, StalePid)
|
end,
|
||||||
end, StalePids),
|
lists:foreach(fun(Pid) -> kick_session(ClientId, Pid) end, ChanPids)
|
||||||
kick_session(ClientId, ChanPid)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
kick_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
|
||||||
case get_chan_info(ClientId, ChanPid) of
|
|
||||||
#{conninfo := #{conn_mod := ConnMod}} ->
|
|
||||||
ConnMod:call(ChanPid, kick);
|
|
||||||
undefined ->
|
|
||||||
{error, not_found}
|
|
||||||
end;
|
|
||||||
|
|
||||||
kick_session(ClientId, ChanPid) ->
|
|
||||||
rpc_call(node(ChanPid), kick_session, [ClientId, ChanPid]).
|
|
||||||
|
|
||||||
%% @doc Is clean start?
|
%% @doc Is clean start?
|
||||||
% is_clean_start(#{clean_start := false}) -> false;
|
% is_clean_start(#{clean_start := false}) -> false;
|
||||||
% is_clean_start(_Attrs) -> true.
|
% is_clean_start(_Attrs) -> true.
|
||||||
|
@ -360,10 +411,16 @@ lookup_channels(local, ClientId) ->
|
||||||
[ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)].
|
[ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)].
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
rpc_call(Node, Fun, Args) ->
|
rpc_call(Node, Fun, Args, Timeout) ->
|
||||||
case rpc:call(Node, ?MODULE, Fun, Args) of
|
case rpc:call(Node, ?MODULE, Fun, Args, 2 * Timeout) of
|
||||||
{badrpc, Reason} -> error(Reason);
|
{badrpc, Reason} ->
|
||||||
Res -> Res
|
%% since eqmx app 4.3.10, the 'kick' and 'discard' calls hanndler
|
||||||
|
%% should catch all exceptions and always return 'ok'.
|
||||||
|
%% This leaves 'badrpc' only possible when there is problem
|
||||||
|
%% calling the remote node.
|
||||||
|
error({badrpc, Reason});
|
||||||
|
Res ->
|
||||||
|
Res
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
|
@ -396,7 +453,7 @@ handle_cast(Msg, State) ->
|
||||||
handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) ->
|
handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) ->
|
||||||
ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
||||||
{Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
|
{Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
|
||||||
ok = emqx_pool:async_submit(fun lists:foreach/2, [fun clean_down/1, Items]),
|
ok = emqx_pool:async_submit(fun lists:foreach/2, [fun ?MODULE:clean_down/1, Items]),
|
||||||
{noreply, State#{chan_pmon := PMon1}};
|
{noreply, State#{chan_pmon := PMon1}};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
@ -432,5 +489,5 @@ get_chann_conn_mod(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
error:badarg -> undefined
|
error:badarg -> undefined
|
||||||
end;
|
end;
|
||||||
get_chann_conn_mod(ClientId, ChanPid) ->
|
get_chann_conn_mod(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), get_chann_conn_mod, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), get_chann_conn_mod, [ClientId, ChanPid], ?T_GET_INFO).
|
||||||
|
|
||||||
|
|
|
@ -16,17 +16,16 @@
|
||||||
|
|
||||||
-module(emqx_congestion).
|
-module(emqx_congestion).
|
||||||
|
|
||||||
-export([ maybe_alarm_port_busy/3
|
-export([ maybe_alarm_conn_congestion/3
|
||||||
, maybe_alarm_port_busy/4
|
|
||||||
, maybe_alarm_too_many_publish/5
|
|
||||||
, maybe_alarm_too_many_publish/6
|
|
||||||
, cancel_alarms/3
|
, cancel_alarms/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(ALARM_CONN_CONGEST(Channel, Reason),
|
-define(ALARM_CONN_CONGEST(Channel, Reason),
|
||||||
list_to_binary(io_lib:format("mqtt_conn/congested/~s/~s/~s", [emqx_channel:info(clientid, Channel),
|
list_to_binary(
|
||||||
maps:get(username, emqx_channel:info(clientinfo, Channel), <<"undefined">>),
|
io_lib:format("~s/~s/~s",
|
||||||
Reason]))).
|
[Reason, emqx_channel:info(clientid, Channel),
|
||||||
|
maps:get(username, emqx_channel:info(clientinfo, Channel),
|
||||||
|
<<"unknown_user">>)]))).
|
||||||
|
|
||||||
-define(ALARM_CONN_INFO_KEYS, [
|
-define(ALARM_CONN_INFO_KEYS, [
|
||||||
socktype, sockname, peername, clientid, username, proto_name, proto_ver,
|
socktype, sockname, peername, clientid, username, proto_name, proto_ver,
|
||||||
|
@ -36,44 +35,28 @@
|
||||||
-define(ALARM_SOCK_OPTS_KEYS, [high_watermark, high_msgq_watermark, sndbuf, recbuf, buffer]).
|
-define(ALARM_SOCK_OPTS_KEYS, [high_watermark, high_msgq_watermark, sndbuf, recbuf, buffer]).
|
||||||
-define(PROC_INFO_KEYS, [message_queue_len, memory, reductions]).
|
-define(PROC_INFO_KEYS, [message_queue_len, memory, reductions]).
|
||||||
-define(ALARM_SENT(REASON), {alarm_sent, REASON}).
|
-define(ALARM_SENT(REASON), {alarm_sent, REASON}).
|
||||||
-define(ALL_ALARM_REASONS, [port_busy, too_many_publish]).
|
-define(ALL_ALARM_REASONS, [conn_congestion]).
|
||||||
-define(CONFIRM_CLEAR(REASON), {alarm_confirm_clear, REASON}).
|
-define(WONT_CLEAR_IN, 60000).
|
||||||
-define(CONFIRM_CLEAR_INTERVAL, 10000).
|
|
||||||
|
|
||||||
maybe_alarm_port_busy(Socket, Transport, Channel) ->
|
maybe_alarm_conn_congestion(Socket, Transport, Channel) ->
|
||||||
maybe_alarm_port_busy(Socket, Transport, Channel, false).
|
case is_alarm_enabled(Channel) of
|
||||||
|
false -> ok;
|
||||||
maybe_alarm_port_busy(Socket, Transport, Channel, ForceClear) ->
|
true ->
|
||||||
case is_tcp_congested(Socket, Transport) of
|
case is_tcp_congested(Socket, Transport) of
|
||||||
true -> alarm_congestion(Socket, Transport, Channel, port_busy);
|
true -> alarm_congestion(Socket, Transport, Channel, conn_congestion);
|
||||||
false -> cancel_alarm_congestion(Socket, Transport, Channel, port_busy,
|
false -> cancel_alarm_congestion(Socket, Transport, Channel, conn_congestion)
|
||||||
ForceClear)
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
maybe_alarm_too_many_publish(Socket, Transport, Channel, PubMsgCount,
|
|
||||||
MaxBatchSize) ->
|
|
||||||
maybe_alarm_too_many_publish(Socket, Transport, Channel, PubMsgCount,
|
|
||||||
MaxBatchSize, false).
|
|
||||||
|
|
||||||
maybe_alarm_too_many_publish(Socket, Transport, Channel, PubMsgCount,
|
|
||||||
PubMsgCount = _MaxBatchSize, _ForceClear) ->
|
|
||||||
%% we only alarm it when the process is "too busy"
|
|
||||||
alarm_congestion(Socket, Transport, Channel, too_many_publish);
|
|
||||||
maybe_alarm_too_many_publish(Socket, Transport, Channel, PubMsgCount,
|
|
||||||
_MaxBatchSize, ForceClear) when PubMsgCount == 0 ->
|
|
||||||
%% but we clear the alarm until it is really "idle", to avoid sending
|
|
||||||
%% alarms and clears too frequently
|
|
||||||
cancel_alarm_congestion(Socket, Transport, Channel, too_many_publish,
|
|
||||||
ForceClear);
|
|
||||||
maybe_alarm_too_many_publish(_Socket, _Transport, _Channel, _PubMsgCount,
|
|
||||||
_MaxBatchSize, _ForceClear) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
cancel_alarms(Socket, Transport, Channel) ->
|
cancel_alarms(Socket, Transport, Channel) ->
|
||||||
lists:foreach(fun(Reason) ->
|
lists:foreach(fun(Reason) ->
|
||||||
do_cancel_alarm_congestion(Socket, Transport, Channel, Reason)
|
do_cancel_alarm_congestion(Socket, Transport, Channel, Reason)
|
||||||
end, ?ALL_ALARM_REASONS).
|
end, ?ALL_ALARM_REASONS).
|
||||||
|
|
||||||
|
is_alarm_enabled(Channel) ->
|
||||||
|
emqx_zone:get_env(emqx_channel:info(zone, Channel),
|
||||||
|
conn_congestion_alarm_enabled, false).
|
||||||
|
|
||||||
alarm_congestion(Socket, Transport, Channel, Reason) ->
|
alarm_congestion(Socket, Transport, Channel, Reason) ->
|
||||||
case has_alarm_sent(Reason) of
|
case has_alarm_sent(Reason) of
|
||||||
false -> do_alarm_congestion(Socket, Transport, Channel, Reason);
|
false -> do_alarm_congestion(Socket, Transport, Channel, Reason);
|
||||||
|
@ -82,8 +65,11 @@ alarm_congestion(Socket, Transport, Channel, Reason) ->
|
||||||
update_alarm_sent_at(Reason)
|
update_alarm_sent_at(Reason)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
cancel_alarm_congestion(Socket, Transport, Channel, Reason, ForceClear) ->
|
cancel_alarm_congestion(Socket, Transport, Channel, Reason) ->
|
||||||
case is_alarm_allowed_clear(Reason, ForceClear) of
|
Zone = emqx_channel:info(zone, Channel),
|
||||||
|
WontClearIn = emqx_zone:get_env(Zone, conn_congestion_min_alarm_sustain_duration,
|
||||||
|
?WONT_CLEAR_IN),
|
||||||
|
case has_alarm_sent(Reason) andalso long_time_since_last_alarm(Reason, WontClearIn) of
|
||||||
true -> do_cancel_alarm_congestion(Socket, Transport, Channel, Reason);
|
true -> do_cancel_alarm_congestion(Socket, Transport, Channel, Reason);
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
@ -123,13 +109,11 @@ get_alarm_sent_at(Reason) ->
|
||||||
LastSentAt -> LastSentAt
|
LastSentAt -> LastSentAt
|
||||||
end.
|
end.
|
||||||
|
|
||||||
is_alarm_allowed_clear(Reason, _ForceClear = true) ->
|
long_time_since_last_alarm(Reason, WontClearIn) ->
|
||||||
has_alarm_sent(Reason);
|
|
||||||
is_alarm_allowed_clear(Reason, _ForceClear = false) ->
|
|
||||||
%% only sent clears when the alarm was not triggered in the last
|
%% only sent clears when the alarm was not triggered in the last
|
||||||
%% ?CONFIRM_CLEAR_INTERVAL time
|
%% WontClearIn time
|
||||||
case timenow() - get_alarm_sent_at(Reason) of
|
case timenow() - get_alarm_sent_at(Reason) of
|
||||||
Elapse when Elapse >= ?CONFIRM_CLEAR_INTERVAL -> true;
|
Elapse when Elapse >= WontClearIn -> true;
|
||||||
_ -> false
|
_ -> false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
, stats/1
|
, stats/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([call/2]).
|
-export([call/2, call/3]).
|
||||||
|
|
||||||
%% Callback
|
%% Callback
|
||||||
-export([init/4]).
|
-export([init/4]).
|
||||||
|
@ -168,7 +168,9 @@ stats(#state{transport = Transport,
|
||||||
lists:append([SockStats, ConnStats, ChanStats, ProcStats]).
|
lists:append([SockStats, ConnStats, ChanStats, ProcStats]).
|
||||||
|
|
||||||
call(Pid, Req) ->
|
call(Pid, Req) ->
|
||||||
gen_server:call(Pid, Req, infinity).
|
call(Pid, Req, infinity).
|
||||||
|
call(Pid, Req, Timeout) ->
|
||||||
|
gen_server:call(Pid, Req, Timeout).
|
||||||
|
|
||||||
stop(Pid) ->
|
stop(Pid) ->
|
||||||
gen_server:stop(Pid).
|
gen_server:stop(Pid).
|
||||||
|
@ -374,12 +376,8 @@ handle_msg({Passive, _Sock}, State)
|
||||||
handle_info(activate_socket, NState1);
|
handle_info(activate_socket, NState1);
|
||||||
|
|
||||||
handle_msg(Deliver = {deliver, _Topic, _Msg},
|
handle_msg(Deliver = {deliver, _Topic, _Msg},
|
||||||
#state{active_n = MaxBatchSize, transport = Transport,
|
#state{active_n = ActiveN} = State) ->
|
||||||
socket = Socket, channel = Channel} = State) ->
|
Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)],
|
||||||
Delivers0 = emqx_misc:drain_deliver(MaxBatchSize),
|
|
||||||
emqx_congestion:maybe_alarm_too_many_publish(Socket, Transport, Channel,
|
|
||||||
length(Delivers0), MaxBatchSize),
|
|
||||||
Delivers = [Deliver|Delivers0],
|
|
||||||
with_channel(handle_deliver, [Delivers], State);
|
with_channel(handle_deliver, [Delivers], State);
|
||||||
|
|
||||||
%% Something sent
|
%% Something sent
|
||||||
|
@ -548,12 +546,9 @@ handle_timeout(_TRef, limit_timeout, State) ->
|
||||||
},
|
},
|
||||||
handle_info(activate_socket, NState);
|
handle_info(activate_socket, NState);
|
||||||
|
|
||||||
handle_timeout(_TRef, emit_stats, State = #state{active_n = MaxBatchSize,
|
handle_timeout(_TRef, emit_stats, State = #state{channel = Channel, transport = Transport,
|
||||||
channel = Channel, transport = Transport, socket = Socket}) ->
|
socket = Socket}) ->
|
||||||
{_, MsgQLen} = erlang:process_info(self(), message_queue_len),
|
emqx_congestion:maybe_alarm_conn_congestion(Socket, Transport, Channel),
|
||||||
emqx_congestion:maybe_alarm_port_busy(Socket, Transport, Channel, true),
|
|
||||||
emqx_congestion:maybe_alarm_too_many_publish(Socket, Transport, Channel,
|
|
||||||
MsgQLen, MaxBatchSize, true),
|
|
||||||
ClientId = emqx_channel:info(clientid, Channel),
|
ClientId = emqx_channel:info(clientid, Channel),
|
||||||
emqx_cm:set_chan_stats(ClientId, stats(State)),
|
emqx_cm:set_chan_stats(ClientId, stats(State)),
|
||||||
{ok, State#state{stats_timer = undefined}};
|
{ok, State#state{stats_timer = undefined}};
|
||||||
|
@ -594,6 +589,11 @@ parse_incoming(Data, Packets, State = #state{parse_state = ParseState}) ->
|
||||||
NState = State#state{parse_state = NParseState},
|
NState = State#state{parse_state = NParseState},
|
||||||
parse_incoming(Rest, [Packet|Packets], NState)
|
parse_incoming(Rest, [Packet|Packets], NState)
|
||||||
catch
|
catch
|
||||||
|
error:proxy_protocol_config_disabled ->
|
||||||
|
?LOG(error,
|
||||||
|
"~nMalformed packet, "
|
||||||
|
"please check proxy_protocol config for specific listeners and zones~n"),
|
||||||
|
{[{frame_error, proxy_protocol_config_disabled} | Packets], State};
|
||||||
error:Reason:Stk ->
|
error:Reason:Stk ->
|
||||||
?LOG(error, "~nParse failed for ~0p~n~0p~nFrame data:~0p",
|
?LOG(error, "~nParse failed for ~0p~n~0p~nFrame data:~0p",
|
||||||
[Reason, Stk, Data]),
|
[Reason, Stk, Data]),
|
||||||
|
@ -666,7 +666,7 @@ send(IoData, #state{transport = Transport, socket = Socket, channel = Channel})
|
||||||
Oct = iolist_size(IoData),
|
Oct = iolist_size(IoData),
|
||||||
ok = emqx_metrics:inc('bytes.sent', Oct),
|
ok = emqx_metrics:inc('bytes.sent', Oct),
|
||||||
emqx_pd:inc_counter(outgoing_bytes, Oct),
|
emqx_pd:inc_counter(outgoing_bytes, Oct),
|
||||||
emqx_congestion:maybe_alarm_port_busy(Socket, Transport, Channel),
|
emqx_congestion:maybe_alarm_conn_congestion(Socket, Transport, Channel),
|
||||||
case Transport:async_send(Socket, IoData, [nosuspend]) of
|
case Transport:async_send(Socket, IoData, [nosuspend]) of
|
||||||
ok -> ok;
|
ok -> ok;
|
||||||
Error = {error, _Reason} ->
|
Error = {error, _Reason} ->
|
||||||
|
@ -690,7 +690,10 @@ handle_info(activate_socket, State = #state{sockstate = OldSst}) ->
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({sock_error, Reason}, State) ->
|
handle_info({sock_error, Reason}, State) ->
|
||||||
Reason =/= closed andalso ?LOG(error, "Socket error: ~p", [Reason]),
|
case Reason =/= closed andalso Reason =/= einval of
|
||||||
|
true -> ?LOG(warning, "socket_error: ~p", [Reason]);
|
||||||
|
false -> ok
|
||||||
|
end,
|
||||||
handle_info({sock_closed, Reason}, close_socket(State));
|
handle_info({sock_closed, Reason}, close_socket(State));
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
@ -814,4 +817,3 @@ stop(Reason, Reply, State) ->
|
||||||
set_field(Name, Value, State) ->
|
set_field(Name, Value, State) ->
|
||||||
Pos = emqx_misc:index_of(Name, record_info(fields, state)),
|
Pos = emqx_misc:index_of(Name, record_info(fields, state)),
|
||||||
setelement(Pos+1, State, Value).
|
setelement(Pos+1, State, Value).
|
||||||
|
|
||||||
|
|
|
@ -67,8 +67,21 @@
|
||||||
version => ?MQTT_PROTO_V4
|
version => ?MQTT_PROTO_V4
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
-define(MULTIPLIER_MAX, 16#200000).
|
||||||
|
|
||||||
|
%% proxy_protocol v1 header human readable
|
||||||
|
-define(PPV1_PROXY, "PROXY ").
|
||||||
|
-define(PPV1_PROXY_UNKNOWN, "PROXY UNKNOWN").
|
||||||
|
%% proxy_protocol v2 header signature:
|
||||||
|
%% 16#0D,16#0A, 16#0D,16#0A,16#00,16#0D,16#0A,16#51,16#55,16#49,16#54,16#0A
|
||||||
|
-define(PPV2_HEADER_SIG, "\r\n\r\n\0\r\nQUIT\n").
|
||||||
|
|
||||||
-dialyzer({no_match, [serialize_utf8_string/2]}).
|
-dialyzer({no_match, [serialize_utf8_string/2]}).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-export([parse_variable_byte_integer/1]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Init Parse State
|
%% Init Parse State
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -96,6 +109,13 @@ parse(Bin) ->
|
||||||
-spec(parse(binary(), parse_state()) -> parse_result()).
|
-spec(parse(binary(), parse_state()) -> parse_result()).
|
||||||
parse(<<>>, {none, Options}) ->
|
parse(<<>>, {none, Options}) ->
|
||||||
{more, {none, Options}};
|
{more, {none, Options}};
|
||||||
|
parse(<<?PPV1_PROXY, IPVer:5/binary, _Rest/binary>>, {none, _Options})
|
||||||
|
when IPVer =:= <<"TCP4 ">> orelse IPVer =:= <<"TCP6 ">> ->
|
||||||
|
error(proxy_protocol_config_disabled);
|
||||||
|
parse(<<?PPV1_PROXY_UNKNOWN, _Rest/binary>>, {none, _Options}) ->
|
||||||
|
error(proxy_protocol_config_disabled);
|
||||||
|
parse(<<?PPV2_HEADER_SIG, _Rest/binary>>, {none, _Options}) ->
|
||||||
|
error(proxy_protocol_config_disabled);
|
||||||
parse(<<Type:4, Dup:1, QoS:2, Retain:1, Rest/binary>>,
|
parse(<<Type:4, Dup:1, QoS:2, Retain:1, Rest/binary>>,
|
||||||
{none, Options = #{strict_mode := StrictMode}}) ->
|
{none, Options = #{strict_mode := StrictMode}}) ->
|
||||||
%% Validate header if strict mode.
|
%% Validate header if strict mode.
|
||||||
|
@ -141,6 +161,9 @@ parse_remaining_len(<<0:8, Rest/binary>>, Header, 1, 0, Options) ->
|
||||||
%% Match PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK...
|
%% Match PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK...
|
||||||
parse_remaining_len(<<0:1, 2:7, Rest/binary>>, Header, 1, 0, Options) ->
|
parse_remaining_len(<<0:1, 2:7, Rest/binary>>, Header, 1, 0, Options) ->
|
||||||
parse_frame(Rest, Header, 2, Options);
|
parse_frame(Rest, Header, 2, Options);
|
||||||
|
parse_remaining_len(<<1:1, _Len:7, _Rest/binary>>, _Header, Multiplier, _Value, _Options)
|
||||||
|
when Multiplier > ?MULTIPLIER_MAX ->
|
||||||
|
error(malformed_variable_byte_integer);
|
||||||
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Header, Multiplier, Value, Options) ->
|
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Header, Multiplier, Value, Options) ->
|
||||||
parse_remaining_len(Rest, Header, Multiplier * ?HIGHBIT, Value + Len * Multiplier, Options);
|
parse_remaining_len(Rest, Header, Multiplier * ?HIGHBIT, Value + Len * Multiplier, Options);
|
||||||
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Header, Multiplier, Value,
|
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Header, Multiplier, Value,
|
||||||
|
@ -177,8 +200,9 @@ packet(Header, Variable) ->
|
||||||
packet(Header, Variable, Payload) ->
|
packet(Header, Variable, Payload) ->
|
||||||
#mqtt_packet{header = Header, variable = Variable, payload = Payload}.
|
#mqtt_packet{header = Header, variable = Variable, payload = Payload}.
|
||||||
|
|
||||||
parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) ->
|
parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin,
|
||||||
{ProtoName, Rest} = parse_utf8_string(FrameBin),
|
#{strict_mode := StrictMode}) ->
|
||||||
|
{ProtoName, Rest} = parse_utf8_string(FrameBin, StrictMode),
|
||||||
<<BridgeTag:4, ProtoVer:4, Rest1/binary>> = Rest,
|
<<BridgeTag:4, ProtoVer:4, Rest1/binary>> = Rest,
|
||||||
% Note: Crash when reserved flag doesn't equal to 0, there is no strict
|
% Note: Crash when reserved flag doesn't equal to 0, there is no strict
|
||||||
% compliance with the MQTT5.0.
|
% compliance with the MQTT5.0.
|
||||||
|
@ -192,8 +216,8 @@ parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) ->
|
||||||
KeepAlive : 16/big,
|
KeepAlive : 16/big,
|
||||||
Rest2/binary>> = Rest1,
|
Rest2/binary>> = Rest1,
|
||||||
|
|
||||||
{Properties, Rest3} = parse_properties(Rest2, ProtoVer),
|
{Properties, Rest3} = parse_properties(Rest2, ProtoVer, StrictMode),
|
||||||
{ClientId, Rest4} = parse_utf8_string(Rest3),
|
{ClientId, Rest4} = parse_utf8_string(Rest3, StrictMode),
|
||||||
ConnPacket = #mqtt_packet_connect{proto_name = ProtoName,
|
ConnPacket = #mqtt_packet_connect{proto_name = ProtoName,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
is_bridge = (BridgeTag =:= 8),
|
is_bridge = (BridgeTag =:= 8),
|
||||||
|
@ -205,14 +229,14 @@ parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) ->
|
||||||
properties = Properties,
|
properties = Properties,
|
||||||
clientid = ClientId
|
clientid = ClientId
|
||||||
},
|
},
|
||||||
{ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4),
|
{ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4, StrictMode),
|
||||||
{Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)),
|
{Username, Rest6} = parse_utf8_string(Rest5, StrictMode, bool(UsernameFlag)),
|
||||||
{Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)),
|
{Passsword, <<>>} = parse_utf8_string(Rest6, StrictMode, bool(PasswordFlag)),
|
||||||
ConnPacket1#mqtt_packet_connect{username = Username, password = Passsword};
|
ConnPacket1#mqtt_packet_connect{username = Username, password = Passsword};
|
||||||
|
|
||||||
parse_packet(#mqtt_packet_header{type = ?CONNACK},
|
parse_packet(#mqtt_packet_header{type = ?CONNACK}, <<AckFlags:8, ReasonCode:8, Rest/binary>>,
|
||||||
<<AckFlags:8, ReasonCode:8, Rest/binary>>, #{version := Ver}) ->
|
#{strict_mode := StrictMode, version := Ver}) ->
|
||||||
{Properties, <<>>} = parse_properties(Rest, Ver),
|
{Properties, <<>>} = parse_properties(Rest, Ver, StrictMode),
|
||||||
#mqtt_packet_connack{ack_flags = AckFlags,
|
#mqtt_packet_connack{ack_flags = AckFlags,
|
||||||
reason_code = ReasonCode,
|
reason_code = ReasonCode,
|
||||||
properties = Properties
|
properties = Properties
|
||||||
|
@ -220,21 +244,22 @@ parse_packet(#mqtt_packet_header{type = ?CONNACK},
|
||||||
|
|
||||||
parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin,
|
parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin,
|
||||||
#{strict_mode := StrictMode, version := Ver}) ->
|
#{strict_mode := StrictMode, version := Ver}) ->
|
||||||
{TopicName, Rest} = parse_utf8_string(Bin),
|
{TopicName, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
{PacketId, Rest1} = case QoS of
|
{PacketId, Rest1} = case QoS of
|
||||||
?QOS_0 -> {undefined, Rest};
|
?QOS_0 -> {undefined, Rest};
|
||||||
_ -> parse_packet_id(Rest)
|
_ -> parse_packet_id(Rest)
|
||||||
end,
|
end,
|
||||||
(PacketId =/= undefined) andalso
|
(PacketId =/= undefined) andalso
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
{Properties, Payload} = parse_properties(Rest1, Ver),
|
{Properties, Payload} = parse_properties(Rest1, Ver, StrictMode),
|
||||||
Publish = #mqtt_packet_publish{topic_name = TopicName,
|
Publish = #mqtt_packet_publish{topic_name = TopicName,
|
||||||
packet_id = PacketId,
|
packet_id = PacketId,
|
||||||
properties = Properties
|
properties = Properties
|
||||||
},
|
},
|
||||||
{Publish, Payload};
|
{Publish, Payload};
|
||||||
|
|
||||||
parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big>>, #{strict_mode := StrictMode})
|
parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big>>,
|
||||||
|
#{strict_mode := StrictMode})
|
||||||
when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP ->
|
when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP ->
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
#mqtt_packet_puback{packet_id = PacketId, reason_code = 0};
|
#mqtt_packet_puback{packet_id = PacketId, reason_code = 0};
|
||||||
|
@ -243,7 +268,7 @@ parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big, ReasonCode,
|
||||||
#{strict_mode := StrictMode, version := Ver = ?MQTT_PROTO_V5})
|
#{strict_mode := StrictMode, version := Ver = ?MQTT_PROTO_V5})
|
||||||
when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP ->
|
when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP ->
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
{Properties, <<>>} = parse_properties(Rest, Ver),
|
{Properties, <<>>} = parse_properties(Rest, Ver, StrictMode),
|
||||||
#mqtt_packet_puback{packet_id = PacketId,
|
#mqtt_packet_puback{packet_id = PacketId,
|
||||||
reason_code = ReasonCode,
|
reason_code = ReasonCode,
|
||||||
properties = Properties
|
properties = Properties
|
||||||
|
@ -252,7 +277,7 @@ parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big, ReasonCode,
|
||||||
parse_packet(#mqtt_packet_header{type = ?SUBSCRIBE}, <<PacketId:16/big, Rest/binary>>,
|
parse_packet(#mqtt_packet_header{type = ?SUBSCRIBE}, <<PacketId:16/big, Rest/binary>>,
|
||||||
#{strict_mode := StrictMode, version := Ver}) ->
|
#{strict_mode := StrictMode, version := Ver}) ->
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
{Properties, Rest1} = parse_properties(Rest, Ver),
|
{Properties, Rest1} = parse_properties(Rest, Ver, StrictMode),
|
||||||
TopicFilters = parse_topic_filters(subscribe, Rest1),
|
TopicFilters = parse_topic_filters(subscribe, Rest1),
|
||||||
ok = validate_subqos([QoS || {_, #{qos := QoS}} <- TopicFilters]),
|
ok = validate_subqos([QoS || {_, #{qos := QoS}} <- TopicFilters]),
|
||||||
#mqtt_packet_subscribe{packet_id = PacketId,
|
#mqtt_packet_subscribe{packet_id = PacketId,
|
||||||
|
@ -263,7 +288,7 @@ parse_packet(#mqtt_packet_header{type = ?SUBSCRIBE}, <<PacketId:16/big, Rest/bin
|
||||||
parse_packet(#mqtt_packet_header{type = ?SUBACK}, <<PacketId:16/big, Rest/binary>>,
|
parse_packet(#mqtt_packet_header{type = ?SUBACK}, <<PacketId:16/big, Rest/binary>>,
|
||||||
#{strict_mode := StrictMode, version := Ver}) ->
|
#{strict_mode := StrictMode, version := Ver}) ->
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
{Properties, Rest1} = parse_properties(Rest, Ver),
|
{Properties, Rest1} = parse_properties(Rest, Ver, StrictMode),
|
||||||
ReasonCodes = parse_reason_codes(Rest1),
|
ReasonCodes = parse_reason_codes(Rest1),
|
||||||
#mqtt_packet_suback{packet_id = PacketId,
|
#mqtt_packet_suback{packet_id = PacketId,
|
||||||
properties = Properties,
|
properties = Properties,
|
||||||
|
@ -273,7 +298,7 @@ parse_packet(#mqtt_packet_header{type = ?SUBACK}, <<PacketId:16/big, Rest/binary
|
||||||
parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <<PacketId:16/big, Rest/binary>>,
|
parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <<PacketId:16/big, Rest/binary>>,
|
||||||
#{strict_mode := StrictMode, version := Ver}) ->
|
#{strict_mode := StrictMode, version := Ver}) ->
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
{Properties, Rest1} = parse_properties(Rest, Ver),
|
{Properties, Rest1} = parse_properties(Rest, Ver, StrictMode),
|
||||||
TopicFilters = parse_topic_filters(unsubscribe, Rest1),
|
TopicFilters = parse_topic_filters(unsubscribe, Rest1),
|
||||||
#mqtt_packet_unsubscribe{packet_id = PacketId,
|
#mqtt_packet_unsubscribe{packet_id = PacketId,
|
||||||
properties = Properties,
|
properties = Properties,
|
||||||
|
@ -288,7 +313,7 @@ parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big>>,
|
||||||
parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big, Rest/binary>>,
|
parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big, Rest/binary>>,
|
||||||
#{strict_mode := StrictMode, version := Ver}) ->
|
#{strict_mode := StrictMode, version := Ver}) ->
|
||||||
StrictMode andalso validate_packet_id(PacketId),
|
StrictMode andalso validate_packet_id(PacketId),
|
||||||
{Properties, Rest1} = parse_properties(Rest, Ver),
|
{Properties, Rest1} = parse_properties(Rest, Ver, StrictMode),
|
||||||
ReasonCodes = parse_reason_codes(Rest1),
|
ReasonCodes = parse_reason_codes(Rest1),
|
||||||
#mqtt_packet_unsuback{packet_id = PacketId,
|
#mqtt_packet_unsuback{packet_id = PacketId,
|
||||||
properties = Properties,
|
properties = Properties,
|
||||||
|
@ -296,118 +321,125 @@ parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big, Rest/bina
|
||||||
};
|
};
|
||||||
|
|
||||||
parse_packet(#mqtt_packet_header{type = ?DISCONNECT}, <<ReasonCode, Rest/binary>>,
|
parse_packet(#mqtt_packet_header{type = ?DISCONNECT}, <<ReasonCode, Rest/binary>>,
|
||||||
#{version := ?MQTT_PROTO_V5}) ->
|
#{strict_mode := StrictMode, version := ?MQTT_PROTO_V5}) ->
|
||||||
{Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5),
|
{Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5, StrictMode),
|
||||||
#mqtt_packet_disconnect{reason_code = ReasonCode,
|
#mqtt_packet_disconnect{reason_code = ReasonCode,
|
||||||
properties = Properties
|
properties = Properties
|
||||||
};
|
};
|
||||||
|
|
||||||
parse_packet(#mqtt_packet_header{type = ?AUTH}, <<ReasonCode, Rest/binary>>,
|
parse_packet(#mqtt_packet_header{type = ?AUTH}, <<ReasonCode, Rest/binary>>,
|
||||||
#{version := ?MQTT_PROTO_V5}) ->
|
#{strict_mode := StrictMode, version := ?MQTT_PROTO_V5}) ->
|
||||||
{Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5),
|
{Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5, StrictMode),
|
||||||
#mqtt_packet_auth{reason_code = ReasonCode, properties = Properties}.
|
#mqtt_packet_auth{reason_code = ReasonCode, properties = Properties}.
|
||||||
|
|
||||||
parse_will_message(Packet = #mqtt_packet_connect{will_flag = true,
|
parse_will_message(Packet = #mqtt_packet_connect{will_flag = true,
|
||||||
proto_ver = Ver}, Bin) ->
|
proto_ver = Ver},
|
||||||
{Props, Rest} = parse_properties(Bin, Ver),
|
Bin, StrictMode) ->
|
||||||
{Topic, Rest1} = parse_utf8_string(Rest),
|
{Props, Rest} = parse_properties(Bin, Ver, StrictMode),
|
||||||
|
{Topic, Rest1} = parse_utf8_string(Rest, StrictMode),
|
||||||
{Payload, Rest2} = parse_binary_data(Rest1),
|
{Payload, Rest2} = parse_binary_data(Rest1),
|
||||||
{Packet#mqtt_packet_connect{will_props = Props,
|
{Packet#mqtt_packet_connect{will_props = Props,
|
||||||
will_topic = Topic,
|
will_topic = Topic,
|
||||||
will_payload = Payload
|
will_payload = Payload
|
||||||
}, Rest2};
|
}, Rest2};
|
||||||
parse_will_message(Packet, Bin) -> {Packet, Bin}.
|
parse_will_message(Packet, Bin, _StrictMode) -> {Packet, Bin}.
|
||||||
|
|
||||||
-compile({inline, [parse_packet_id/1]}).
|
-compile({inline, [parse_packet_id/1]}).
|
||||||
parse_packet_id(<<PacketId:16/big, Rest/binary>>) ->
|
parse_packet_id(<<PacketId:16/big, Rest/binary>>) ->
|
||||||
{PacketId, Rest}.
|
{PacketId, Rest}.
|
||||||
|
|
||||||
parse_properties(Bin, Ver) when Ver =/= ?MQTT_PROTO_V5 ->
|
parse_properties(Bin, Ver, _StrictMode) when Ver =/= ?MQTT_PROTO_V5 ->
|
||||||
{#{}, Bin};
|
{#{}, Bin};
|
||||||
%% TODO: version mess?
|
%% TODO: version mess?
|
||||||
parse_properties(<<>>, ?MQTT_PROTO_V5) ->
|
parse_properties(<<>>, ?MQTT_PROTO_V5, _StrictMode) ->
|
||||||
{#{}, <<>>};
|
{#{}, <<>>};
|
||||||
parse_properties(<<0, Rest/binary>>, ?MQTT_PROTO_V5) ->
|
parse_properties(<<0, Rest/binary>>, ?MQTT_PROTO_V5, _StrictMode) ->
|
||||||
{#{}, Rest};
|
{#{}, Rest};
|
||||||
parse_properties(Bin, ?MQTT_PROTO_V5) ->
|
parse_properties(Bin, ?MQTT_PROTO_V5, StrictMode) ->
|
||||||
{Len, Rest} = parse_variable_byte_integer(Bin),
|
{Len, Rest} = parse_variable_byte_integer(Bin),
|
||||||
<<PropsBin:Len/binary, Rest1/binary>> = Rest,
|
<<PropsBin:Len/binary, Rest1/binary>> = Rest,
|
||||||
{parse_property(PropsBin, #{}), Rest1}.
|
{parse_property(PropsBin, #{}, StrictMode), Rest1}.
|
||||||
|
|
||||||
parse_property(<<>>, Props) ->
|
parse_property(<<>>, Props, _StrictMode) ->
|
||||||
Props;
|
Props;
|
||||||
parse_property(<<16#01, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#01, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Payload-Format-Indicator' => Val});
|
parse_property(Bin, Props#{'Payload-Format-Indicator' => Val}, StrictMode);
|
||||||
parse_property(<<16#02, Val:32/big, Bin/binary>>, Props) ->
|
parse_property(<<16#02, Val:32/big, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Message-Expiry-Interval' => Val});
|
parse_property(Bin, Props#{'Message-Expiry-Interval' => Val}, StrictMode);
|
||||||
parse_property(<<16#03, Bin/binary>>, Props) ->
|
parse_property(<<16#03, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Content-Type' => Val});
|
parse_property(Rest, Props#{'Content-Type' => Val}, StrictMode);
|
||||||
parse_property(<<16#08, Bin/binary>>, Props) ->
|
parse_property(<<16#08, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Response-Topic' => Val});
|
parse_property(Rest, Props#{'Response-Topic' => Val}, StrictMode);
|
||||||
parse_property(<<16#09, Len:16/big, Val:Len/binary, Bin/binary>>, Props) ->
|
parse_property(<<16#09, Len:16/big, Val:Len/binary, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Correlation-Data' => Val});
|
parse_property(Bin, Props#{'Correlation-Data' => Val}, StrictMode);
|
||||||
parse_property(<<16#0B, Bin/binary>>, Props) ->
|
parse_property(<<16#0B, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_variable_byte_integer(Bin),
|
{Val, Rest} = parse_variable_byte_integer(Bin),
|
||||||
parse_property(Rest, Props#{'Subscription-Identifier' => Val});
|
parse_property(Rest, Props#{'Subscription-Identifier' => Val}, StrictMode);
|
||||||
parse_property(<<16#11, Val:32/big, Bin/binary>>, Props) ->
|
parse_property(<<16#11, Val:32/big, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Session-Expiry-Interval' => Val});
|
parse_property(Bin, Props#{'Session-Expiry-Interval' => Val}, StrictMode);
|
||||||
parse_property(<<16#12, Bin/binary>>, Props) ->
|
parse_property(<<16#12, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Assigned-Client-Identifier' => Val});
|
parse_property(Rest, Props#{'Assigned-Client-Identifier' => Val}, StrictMode);
|
||||||
parse_property(<<16#13, Val:16, Bin/binary>>, Props) ->
|
parse_property(<<16#13, Val:16, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Server-Keep-Alive' => Val});
|
parse_property(Bin, Props#{'Server-Keep-Alive' => Val}, StrictMode);
|
||||||
parse_property(<<16#15, Bin/binary>>, Props) ->
|
parse_property(<<16#15, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Authentication-Method' => Val});
|
parse_property(Rest, Props#{'Authentication-Method' => Val}, StrictMode);
|
||||||
parse_property(<<16#16, Len:16/big, Val:Len/binary, Bin/binary>>, Props) ->
|
parse_property(<<16#16, Len:16/big, Val:Len/binary, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Authentication-Data' => Val});
|
parse_property(Bin, Props#{'Authentication-Data' => Val}, StrictMode);
|
||||||
parse_property(<<16#17, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#17, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Request-Problem-Information' => Val});
|
parse_property(Bin, Props#{'Request-Problem-Information' => Val}, StrictMode);
|
||||||
parse_property(<<16#18, Val:32, Bin/binary>>, Props) ->
|
parse_property(<<16#18, Val:32, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Will-Delay-Interval' => Val});
|
parse_property(Bin, Props#{'Will-Delay-Interval' => Val}, StrictMode);
|
||||||
parse_property(<<16#19, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#19, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Request-Response-Information' => Val});
|
parse_property(Bin, Props#{'Request-Response-Information' => Val}, StrictMode);
|
||||||
parse_property(<<16#1A, Bin/binary>>, Props) ->
|
parse_property(<<16#1A, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Response-Information' => Val});
|
parse_property(Rest, Props#{'Response-Information' => Val}, StrictMode);
|
||||||
parse_property(<<16#1C, Bin/binary>>, Props) ->
|
parse_property(<<16#1C, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Server-Reference' => Val});
|
parse_property(Rest, Props#{'Server-Reference' => Val}, StrictMode);
|
||||||
parse_property(<<16#1F, Bin/binary>>, Props) ->
|
parse_property(<<16#1F, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Val, Rest} = parse_utf8_string(Bin),
|
{Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
||||||
parse_property(Rest, Props#{'Reason-String' => Val});
|
parse_property(Rest, Props#{'Reason-String' => Val}, StrictMode);
|
||||||
parse_property(<<16#21, Val:16/big, Bin/binary>>, Props) ->
|
parse_property(<<16#21, Val:16/big, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Receive-Maximum' => Val});
|
parse_property(Bin, Props#{'Receive-Maximum' => Val}, StrictMode);
|
||||||
parse_property(<<16#22, Val:16/big, Bin/binary>>, Props) ->
|
parse_property(<<16#22, Val:16/big, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Topic-Alias-Maximum' => Val});
|
parse_property(Bin, Props#{'Topic-Alias-Maximum' => Val}, StrictMode);
|
||||||
parse_property(<<16#23, Val:16/big, Bin/binary>>, Props) ->
|
parse_property(<<16#23, Val:16/big, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Topic-Alias' => Val});
|
parse_property(Bin, Props#{'Topic-Alias' => Val}, StrictMode);
|
||||||
parse_property(<<16#24, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#24, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Maximum-QoS' => Val});
|
parse_property(Bin, Props#{'Maximum-QoS' => Val}, StrictMode);
|
||||||
parse_property(<<16#25, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#25, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Retain-Available' => Val});
|
parse_property(Bin, Props#{'Retain-Available' => Val}, StrictMode);
|
||||||
parse_property(<<16#26, Bin/binary>>, Props) ->
|
parse_property(<<16#26, Bin/binary>>, Props, StrictMode) ->
|
||||||
{Pair, Rest} = parse_utf8_pair(Bin),
|
{Pair, Rest} = parse_utf8_pair(Bin, StrictMode),
|
||||||
case maps:find('User-Property', Props) of
|
case maps:find('User-Property', Props) of
|
||||||
{ok, UserProps} ->
|
{ok, UserProps} ->
|
||||||
UserProps1 = lists:append(UserProps, [Pair]),
|
UserProps1 = lists:append(UserProps, [Pair]),
|
||||||
parse_property(Rest, Props#{'User-Property' := UserProps1});
|
parse_property(Rest, Props#{'User-Property' := UserProps1}, StrictMode);
|
||||||
error ->
|
error ->
|
||||||
parse_property(Rest, Props#{'User-Property' => [Pair]})
|
parse_property(Rest, Props#{'User-Property' => [Pair]}, StrictMode)
|
||||||
end;
|
end;
|
||||||
parse_property(<<16#27, Val:32, Bin/binary>>, Props) ->
|
parse_property(<<16#27, Val:32, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Maximum-Packet-Size' => Val});
|
parse_property(Bin, Props#{'Maximum-Packet-Size' => Val}, StrictMode);
|
||||||
parse_property(<<16#28, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#28, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Wildcard-Subscription-Available' => Val});
|
parse_property(Bin, Props#{'Wildcard-Subscription-Available' => Val}, StrictMode);
|
||||||
parse_property(<<16#29, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#29, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Subscription-Identifier-Available' => Val});
|
parse_property(Bin, Props#{'Subscription-Identifier-Available' => Val}, StrictMode);
|
||||||
parse_property(<<16#2A, Val, Bin/binary>>, Props) ->
|
parse_property(<<16#2A, Val, Bin/binary>>, Props, StrictMode) ->
|
||||||
parse_property(Bin, Props#{'Shared-Subscription-Available' => Val}).
|
parse_property(Bin, Props#{'Shared-Subscription-Available' => Val}, StrictMode);
|
||||||
|
parse_property(<<Property:8, _Rest/binary>>, _Props, _StrictMode) ->
|
||||||
|
error(#{invalid_property_code => Property}).
|
||||||
|
%% TODO: invalid property in specific packet.
|
||||||
|
|
||||||
parse_variable_byte_integer(Bin) ->
|
parse_variable_byte_integer(Bin) ->
|
||||||
parse_variable_byte_integer(Bin, 1, 0).
|
parse_variable_byte_integer(Bin, 1, 0).
|
||||||
|
parse_variable_byte_integer(<<1:1, _Len:7, _Rest/binary>>, Multiplier, _Value)
|
||||||
|
when Multiplier > ?MULTIPLIER_MAX ->
|
||||||
|
error(malformed_variable_byte_integer);
|
||||||
parse_variable_byte_integer(<<1:1, Len:7, Rest/binary>>, Multiplier, Value) ->
|
parse_variable_byte_integer(<<1:1, Len:7, Rest/binary>>, Multiplier, Value) ->
|
||||||
parse_variable_byte_integer(Rest, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
|
parse_variable_byte_integer(Rest, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
|
||||||
parse_variable_byte_integer(<<0:1, Len:7, Rest/binary>>, Multiplier, Value) ->
|
parse_variable_byte_integer(<<0:1, Len:7, Rest/binary>>, Multiplier, Value) ->
|
||||||
|
@ -423,20 +455,53 @@ parse_topic_filters(unsubscribe, Bin) ->
|
||||||
parse_reason_codes(Bin) ->
|
parse_reason_codes(Bin) ->
|
||||||
[Code || <<Code>> <= Bin].
|
[Code || <<Code>> <= Bin].
|
||||||
|
|
||||||
parse_utf8_pair(<<Len1:16/big, Key:Len1/binary,
|
%%--------------------
|
||||||
Len2:16/big, Val:Len2/binary, Rest/binary>>) ->
|
%% parse utf8 pair
|
||||||
{{Key, Val}, Rest}.
|
parse_utf8_pair( <<Len1:16/big, Key:Len1/binary,
|
||||||
|
Len2:16/big, Val:Len2/binary, Rest/binary>>
|
||||||
|
, true) ->
|
||||||
|
{{validate_utf8(Key), validate_utf8(Val)}, Rest};
|
||||||
|
parse_utf8_pair( <<Len1:16/big, Key:Len1/binary,
|
||||||
|
Len2:16/big, Val:Len2/binary, Rest/binary>>
|
||||||
|
, false) ->
|
||||||
|
{{Key, Val}, Rest};
|
||||||
|
parse_utf8_pair(<<LenK:16/big, Rest/binary>>, _StrictMode)
|
||||||
|
when LenK > byte_size(Rest) ->
|
||||||
|
error(user_property_not_enough_bytes);
|
||||||
|
parse_utf8_pair(<<LenK:16/big, _Key:LenK/binary, %% key maybe malformed
|
||||||
|
LenV:16/big, Rest/binary>>, _StrictMode)
|
||||||
|
when LenV > byte_size(Rest) ->
|
||||||
|
error(malformed_user_property_value);
|
||||||
|
parse_utf8_pair(Bin, _StrictMode)
|
||||||
|
when 4 > byte_size(Bin) ->
|
||||||
|
error(user_property_not_enough_bytes).
|
||||||
|
|
||||||
parse_utf8_string(Bin, false) ->
|
%%--------------------
|
||||||
|
%% parse utf8 string
|
||||||
|
parse_utf8_string(Bin, _StrictMode, false) ->
|
||||||
{undefined, Bin};
|
{undefined, Bin};
|
||||||
parse_utf8_string(Bin, true) ->
|
parse_utf8_string(Bin, StrictMode, true) ->
|
||||||
parse_utf8_string(Bin).
|
parse_utf8_string(Bin, StrictMode).
|
||||||
|
|
||||||
parse_utf8_string(<<Len:16/big, Str:Len/binary, Rest/binary>>) ->
|
parse_utf8_string(<<Len:16/big, Str:Len/binary, Rest/binary>>, true) ->
|
||||||
{Str, Rest}.
|
{validate_utf8(Str), Rest};
|
||||||
|
parse_utf8_string(<<Len:16/big, Str:Len/binary, Rest/binary>>, false) ->
|
||||||
|
{Str, Rest};
|
||||||
|
parse_utf8_string(<<Len:16/big, Rest/binary>>, _)
|
||||||
|
when Len > byte_size(Rest) ->
|
||||||
|
error(malformed_utf8_string);
|
||||||
|
parse_utf8_string(Bin, _)
|
||||||
|
when 2 > byte_size(Bin) ->
|
||||||
|
error(malformed_utf8_string_length).
|
||||||
|
|
||||||
parse_binary_data(<<Len:16/big, Data:Len/binary, Rest/binary>>) ->
|
parse_binary_data(<<Len:16/big, Data:Len/binary, Rest/binary>>) ->
|
||||||
{Data, Rest}.
|
{Data, Rest};
|
||||||
|
parse_binary_data(<<Len:16/big, Rest/binary>>)
|
||||||
|
when Len > byte_size(Rest) ->
|
||||||
|
error(malformed_binary_data);
|
||||||
|
parse_binary_data(Bin)
|
||||||
|
when 2 > byte_size(Bin) ->
|
||||||
|
error(malformed_binary_data_length).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Serialize MQTT Packet
|
%% Serialize MQTT Packet
|
||||||
|
@ -774,3 +839,52 @@ fixqos(?PUBREL, 0) -> 1;
|
||||||
fixqos(?SUBSCRIBE, 0) -> 1;
|
fixqos(?SUBSCRIBE, 0) -> 1;
|
||||||
fixqos(?UNSUBSCRIBE, 0) -> 1;
|
fixqos(?UNSUBSCRIBE, 0) -> 1;
|
||||||
fixqos(_Type, QoS) -> QoS.
|
fixqos(_Type, QoS) -> QoS.
|
||||||
|
|
||||||
|
validate_utf8(Bin) ->
|
||||||
|
case unicode:characters_to_binary(Bin) of
|
||||||
|
{error, _, _} ->
|
||||||
|
error(utf8_string_invalid);
|
||||||
|
{incomplete, _, _} ->
|
||||||
|
error(utf8_string_invalid);
|
||||||
|
Bin when is_binary(Bin) ->
|
||||||
|
case validate_mqtt_utf8_char(Bin) of
|
||||||
|
true -> Bin;
|
||||||
|
false -> error(utf8_string_invalid)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Is the utf8 string respecting UTF-8 characters defined by MQTT Spec?
|
||||||
|
%% i.e. contains invalid UTF-8 char or control char
|
||||||
|
validate_mqtt_utf8_char(<<>>) ->
|
||||||
|
true;
|
||||||
|
%% ==== 1-Byte UTF-8 invalid: [[U+0000 .. U+001F] && [U+007F]]
|
||||||
|
validate_mqtt_utf8_char(<<B1, Bs/binary>>)
|
||||||
|
when B1 >= 16#20, B1 =< 16#7E ->
|
||||||
|
validate_mqtt_utf8_char(Bs);
|
||||||
|
validate_mqtt_utf8_char(<<B1, _Bs/binary>>)
|
||||||
|
when B1 >= 16#00, B1 =< 16#1F;
|
||||||
|
B1 =:= 16#7F ->
|
||||||
|
%% [U+0000 .. U+001F] && [U+007F]
|
||||||
|
false;
|
||||||
|
%% ==== 2-Bytes UTF-8 invalid: [U+0080 .. U+009F]
|
||||||
|
validate_mqtt_utf8_char(<<B1, B2, Bs/binary>>)
|
||||||
|
when B1 =:= 16#C2;
|
||||||
|
B2 >= 16#A0, B2 =< 16#BF;
|
||||||
|
B1 > 16#C3, B1 =< 16#DE;
|
||||||
|
B2 >= 16#80, B2 =< 16#BF ->
|
||||||
|
validate_mqtt_utf8_char(Bs);
|
||||||
|
validate_mqtt_utf8_char(<<16#C2, B2, _Bs/binary>>)
|
||||||
|
when B2 >= 16#80, B2 =< 16#9F ->
|
||||||
|
%% [U+0080 .. U+009F]
|
||||||
|
false;
|
||||||
|
%% ==== 3-Bytes UTF-8 invalid: [U+D800 .. U+DFFF]
|
||||||
|
validate_mqtt_utf8_char(<<B1, _B2, _B3, Bs/binary>>)
|
||||||
|
when B1 >= 16#E0, B1 =< 16#EE;
|
||||||
|
B1 =:= 16#EF ->
|
||||||
|
validate_mqtt_utf8_char(Bs);
|
||||||
|
validate_mqtt_utf8_char(<<16#ED, _B2, _B3, _Bs/binary>>) ->
|
||||||
|
false;
|
||||||
|
%% ==== 4-Bytes UTF-8
|
||||||
|
validate_mqtt_utf8_char(<<B1, _B2, _B3, _B4, Bs/binary>>)
|
||||||
|
when B1 =:= 16#0F ->
|
||||||
|
validate_mqtt_utf8_char(Bs).
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
, init/4 %% XXX: Compatible with before 4.2 version
|
, init/4 %% XXX: Compatible with before 4.2 version
|
||||||
, info/1
|
, info/1
|
||||||
, check/2
|
, check/2
|
||||||
|
, update_overall_limiter/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-record(limiter, {
|
-record(limiter, {
|
||||||
|
@ -152,3 +153,15 @@ is_message_limiter(conn_messages_in) -> true;
|
||||||
is_message_limiter(conn_messages_routing) -> true;
|
is_message_limiter(conn_messages_routing) -> true;
|
||||||
is_message_limiter(overall_messages_routing) -> true;
|
is_message_limiter(overall_messages_routing) -> true;
|
||||||
is_message_limiter(_) -> false.
|
is_message_limiter(_) -> false.
|
||||||
|
|
||||||
|
update_overall_limiter(Zone, Name, Capacity, Interval) ->
|
||||||
|
case is_overall_limiter(Name) of
|
||||||
|
false -> false;
|
||||||
|
_ ->
|
||||||
|
try
|
||||||
|
esockd_limiter:update({Zone, Name}, Capacity, Interval),
|
||||||
|
true
|
||||||
|
catch _:_:_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
, index_of/2
|
, index_of/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(OOM_FACTOR, 1.25).
|
||||||
|
|
||||||
%% @doc Merge options
|
%% @doc Merge options
|
||||||
-spec(merge_opts(Opts, Opts) -> Opts when Opts :: proplists:proplist()).
|
-spec(merge_opts(Opts, Opts) -> Opts when Opts :: proplists:proplist()).
|
||||||
merge_opts(Defaults, Options) ->
|
merge_opts(Defaults, Options) ->
|
||||||
|
@ -185,8 +187,8 @@ do_check_oom([{Val, Max, Reason}|Rest]) ->
|
||||||
|
|
||||||
tune_heap_size(#{max_heap_size := MaxHeapSize}) ->
|
tune_heap_size(#{max_heap_size := MaxHeapSize}) ->
|
||||||
%% If set to zero, the limit is disabled.
|
%% If set to zero, the limit is disabled.
|
||||||
erlang:process_flag(max_heap_size, #{size => MaxHeapSize,
|
erlang:process_flag(max_heap_size, #{size => must_kill_heap_size(MaxHeapSize),
|
||||||
kill => false,
|
kill => true,
|
||||||
error_logger => true
|
error_logger => true
|
||||||
});
|
});
|
||||||
tune_heap_size(undefined) -> ok.
|
tune_heap_size(undefined) -> ok.
|
||||||
|
@ -233,3 +235,19 @@ index_of(E, I, [E|_]) ->
|
||||||
index_of(E, I, [_|L]) ->
|
index_of(E, I, [_|L]) ->
|
||||||
index_of(E, I+1, L).
|
index_of(E, I+1, L).
|
||||||
|
|
||||||
|
must_kill_heap_size(Size) ->
|
||||||
|
%% We set the max allowed heap size by `erlang:process_flag(max_heap_size, #{size => Size})`,
|
||||||
|
%% where the `Size` cannot be set to an integer lager than `(1 bsl 59) - 1` on a 64-bit system,
|
||||||
|
%% or `(1 bsl 27) - 1` on a 32-bit system.
|
||||||
|
MaxAllowedSize = case erlang:system_info(wordsize) of
|
||||||
|
8 -> % arch_64
|
||||||
|
(1 bsl 59) - 1;
|
||||||
|
4 -> % arch_32
|
||||||
|
(1 bsl 27) - 1
|
||||||
|
end,
|
||||||
|
%% We multiply the size with factor ?OOM_FACTOR, to give the
|
||||||
|
%% process a chance to suicide by `check_oom/1`
|
||||||
|
case ceil(Size * ?OOM_FACTOR) of
|
||||||
|
Size0 when Size0 >= MaxAllowedSize -> MaxAllowedSize;
|
||||||
|
Size0 -> Size0
|
||||||
|
end.
|
||||||
|
|
|
@ -67,6 +67,9 @@
|
||||||
, dropped/1
|
, dropped/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([ live_upgrade/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export_type([mqueue/0, options/0]).
|
-export_type([mqueue/0, options/0]).
|
||||||
|
|
||||||
-type(topic() :: emqx_topic:topic()).
|
-type(topic() :: emqx_topic:topic()).
|
||||||
|
@ -91,6 +94,11 @@
|
||||||
-define(MAX_LEN_INFINITY, 0).
|
-define(MAX_LEN_INFINITY, 0).
|
||||||
-define(INFO_KEYS, [store_qos0, max_len, len, dropped]).
|
-define(INFO_KEYS, [store_qos0, max_len, len, dropped]).
|
||||||
|
|
||||||
|
-record(shift_opts, {
|
||||||
|
multiplier :: non_neg_integer(),
|
||||||
|
base :: integer()
|
||||||
|
}).
|
||||||
|
|
||||||
-record(mqueue, {
|
-record(mqueue, {
|
||||||
store_qos0 = false :: boolean(),
|
store_qos0 = false :: boolean(),
|
||||||
max_len = ?MAX_LEN_INFINITY :: count(),
|
max_len = ?MAX_LEN_INFINITY :: count(),
|
||||||
|
@ -98,11 +106,16 @@
|
||||||
dropped = 0 :: count(),
|
dropped = 0 :: count(),
|
||||||
p_table = ?NO_PRIORITY_TABLE :: p_table(),
|
p_table = ?NO_PRIORITY_TABLE :: p_table(),
|
||||||
default_p = ?LOWEST_PRIORITY :: priority(),
|
default_p = ?LOWEST_PRIORITY :: priority(),
|
||||||
q = ?PQUEUE:new() :: pq()
|
q = ?PQUEUE:new() :: pq(),
|
||||||
|
shift_opts :: #shift_opts{},
|
||||||
|
last_p :: non_neg_integer() | undefined,
|
||||||
|
counter :: non_neg_integer() | undefined
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type(mqueue() :: #mqueue{}).
|
-type(mqueue() :: #mqueue{}).
|
||||||
|
|
||||||
|
-define(OLD(Q), Q = {mqueue, _, _, _, _, _, _, _}).
|
||||||
|
|
||||||
-spec(init(options()) -> mqueue()).
|
-spec(init(options()) -> mqueue()).
|
||||||
init(Opts = #{max_len := MaxLen0, store_qos0 := QoS_0}) ->
|
init(Opts = #{max_len := MaxLen0, store_qos0 := QoS_0}) ->
|
||||||
MaxLen = case (is_integer(MaxLen0) andalso MaxLen0 > ?MAX_LEN_INFINITY) of
|
MaxLen = case (is_integer(MaxLen0) andalso MaxLen0 > ?MAX_LEN_INFINITY) of
|
||||||
|
@ -112,7 +125,8 @@ init(Opts = #{max_len := MaxLen0, store_qos0 := QoS_0}) ->
|
||||||
#mqueue{max_len = MaxLen,
|
#mqueue{max_len = MaxLen,
|
||||||
store_qos0 = QoS_0,
|
store_qos0 = QoS_0,
|
||||||
p_table = get_opt(priorities, Opts, ?NO_PRIORITY_TABLE),
|
p_table = get_opt(priorities, Opts, ?NO_PRIORITY_TABLE),
|
||||||
default_p = get_priority_opt(Opts)
|
default_p = get_priority_opt(Opts),
|
||||||
|
shift_opts = get_shift_opt(Opts)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
-spec(info(mqueue()) -> emqx_types:infos()).
|
-spec(info(mqueue()) -> emqx_types:infos()).
|
||||||
|
@ -127,22 +141,30 @@ info(max_len, #mqueue{max_len = MaxLen}) ->
|
||||||
info(len, #mqueue{len = Len}) ->
|
info(len, #mqueue{len = Len}) ->
|
||||||
Len;
|
Len;
|
||||||
info(dropped, #mqueue{dropped = Dropped}) ->
|
info(dropped, #mqueue{dropped = Dropped}) ->
|
||||||
Dropped.
|
Dropped;
|
||||||
|
info(Info, ?OLD(MQ)) ->
|
||||||
|
info(Info, live_upgrade(MQ)).
|
||||||
|
|
||||||
is_empty(#mqueue{len = Len}) -> Len =:= 0.
|
is_empty(#mqueue{len = Len}) -> Len =:= 0;
|
||||||
|
is_empty(?OLD(MQ)) -> is_empty(live_upgrade(MQ)).
|
||||||
|
|
||||||
len(#mqueue{len = Len}) -> Len.
|
len(#mqueue{len = Len}) -> Len;
|
||||||
|
len(?OLD(MQ)) -> len(live_upgrade(MQ)).
|
||||||
|
|
||||||
max_len(#mqueue{max_len = MaxLen}) -> MaxLen.
|
max_len(#mqueue{max_len = MaxLen}) -> MaxLen;
|
||||||
|
max_len(?OLD(MQ)) -> max_len(live_upgrade(MQ)).
|
||||||
|
|
||||||
%% @doc Return number of dropped messages.
|
%% @doc Return number of dropped messages.
|
||||||
-spec(dropped(mqueue()) -> count()).
|
-spec(dropped(mqueue()) -> count()).
|
||||||
dropped(#mqueue{dropped = Dropped}) -> Dropped.
|
dropped(#mqueue{dropped = Dropped}) -> Dropped;
|
||||||
|
dropped(?OLD(MQ)) -> dropped(live_upgrade(MQ)).
|
||||||
|
|
||||||
%% @doc Stats of the mqueue
|
%% @doc Stats of the mqueue
|
||||||
-spec(stats(mqueue()) -> [stat()]).
|
-spec(stats(mqueue()) -> [stat()]).
|
||||||
stats(#mqueue{max_len = MaxLen, dropped = Dropped} = MQ) ->
|
stats(#mqueue{max_len = MaxLen, dropped = Dropped} = MQ) ->
|
||||||
[{len, len(MQ)}, {max_len, MaxLen}, {dropped, Dropped}].
|
[{len, len(MQ)}, {max_len, MaxLen}, {dropped, Dropped}];
|
||||||
|
stats(?OLD(MQ)) ->
|
||||||
|
stats(live_upgrade(MQ)).
|
||||||
|
|
||||||
%% @doc Enqueue a message.
|
%% @doc Enqueue a message.
|
||||||
-spec(in(message(), mqueue()) -> {maybe(message()), mqueue()}).
|
-spec(in(message(), mqueue()) -> {maybe(message()), mqueue()}).
|
||||||
|
@ -165,15 +187,34 @@ in(Msg = #message{topic = Topic}, MQ = #mqueue{default_p = Dp,
|
||||||
{DroppedMsg, MQ#mqueue{q = Q2, dropped = Dropped + 1}};
|
{DroppedMsg, MQ#mqueue{q = Q2, dropped = Dropped + 1}};
|
||||||
false ->
|
false ->
|
||||||
{_DroppedMsg = undefined, MQ#mqueue{len = Len + 1, q = ?PQUEUE:in(Msg, Priority, Q)}}
|
{_DroppedMsg = undefined, MQ#mqueue{len = Len + 1, q = ?PQUEUE:in(Msg, Priority, Q)}}
|
||||||
end.
|
end;
|
||||||
|
in(Msg, ?OLD(MQ)) ->
|
||||||
|
in(Msg, live_upgrade(MQ)).
|
||||||
|
|
||||||
-spec(out(mqueue()) -> {empty | {value, message()}, mqueue()}).
|
-spec(out(mqueue()) -> {empty | {value, message()}, mqueue()}).
|
||||||
out(MQ = #mqueue{len = 0, q = Q}) ->
|
out(MQ = #mqueue{len = 0, q = Q}) ->
|
||||||
0 = ?PQUEUE:len(Q), %% assert, in this case, ?PQUEUE:len should be very cheap
|
0 = ?PQUEUE:len(Q), %% assert, in this case, ?PQUEUE:len should be very cheap
|
||||||
{empty, MQ};
|
{empty, MQ};
|
||||||
out(MQ = #mqueue{q = Q, len = Len}) ->
|
out(MQ = #mqueue{q = Q, len = Len, last_p = undefined, shift_opts = ShiftOpts}) ->
|
||||||
|
{{value, Val, Prio}, Q1} = ?PQUEUE:out_p(Q), %% Shouldn't fail, since we've checked the length
|
||||||
|
MQ1 = MQ#mqueue{
|
||||||
|
q = Q1,
|
||||||
|
len = Len - 1,
|
||||||
|
last_p = Prio,
|
||||||
|
counter = init_counter(Prio, ShiftOpts)
|
||||||
|
},
|
||||||
|
{{value, Val}, MQ1};
|
||||||
|
out(MQ = #mqueue{q = Q, counter = 0}) ->
|
||||||
|
MQ1 = MQ#mqueue{
|
||||||
|
q = ?PQUEUE:shift(Q),
|
||||||
|
last_p = undefined
|
||||||
|
},
|
||||||
|
out(MQ1);
|
||||||
|
out(MQ = #mqueue{q = Q, len = Len, counter = Cnt}) ->
|
||||||
{R, Q1} = ?PQUEUE:out(Q),
|
{R, Q1} = ?PQUEUE:out(Q),
|
||||||
{R, MQ#mqueue{q = Q1, len = Len - 1}}.
|
{R, MQ#mqueue{q = Q1, len = Len - 1, counter = Cnt - 1}};
|
||||||
|
out(?OLD(MQ)) ->
|
||||||
|
out(live_upgrade(MQ)).
|
||||||
|
|
||||||
get_opt(Key, Opts, Default) ->
|
get_opt(Key, Opts, Default) ->
|
||||||
case maps:get(Key, Opts, Default) of
|
case maps:get(Key, Opts, Default) of
|
||||||
|
@ -194,3 +235,46 @@ get_priority_opt(Opts) ->
|
||||||
%% while the highest 'infinity' is a [{infinity, queue:queue()}]
|
%% while the highest 'infinity' is a [{infinity, queue:queue()}]
|
||||||
get_priority(_Topic, ?NO_PRIORITY_TABLE, _) -> ?LOWEST_PRIORITY;
|
get_priority(_Topic, ?NO_PRIORITY_TABLE, _) -> ?LOWEST_PRIORITY;
|
||||||
get_priority(Topic, PTab, Dp) -> maps:get(Topic, PTab, Dp).
|
get_priority(Topic, PTab, Dp) -> maps:get(Topic, PTab, Dp).
|
||||||
|
|
||||||
|
init_counter(?HIGHEST_PRIORITY, Opts) ->
|
||||||
|
Infinity = 1000000,
|
||||||
|
init_counter(Infinity, Opts);
|
||||||
|
init_counter(Prio, #shift_opts{multiplier = Mult, base = Base}) ->
|
||||||
|
(Prio + Base) * Mult.
|
||||||
|
|
||||||
|
get_shift_opt(Opts) ->
|
||||||
|
Mult = maps:get(shift_multiplier, Opts, 10),
|
||||||
|
Min = case Opts of
|
||||||
|
#{p_table := PTab} ->
|
||||||
|
case maps:size(PTab) of
|
||||||
|
0 -> 0;
|
||||||
|
_ -> lists:min(maps:values(PTab))
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
?LOWEST_PRIORITY
|
||||||
|
end,
|
||||||
|
Base = case Min < 0 of
|
||||||
|
true -> -Min;
|
||||||
|
false -> 0
|
||||||
|
end,
|
||||||
|
#shift_opts{
|
||||||
|
multiplier = Mult,
|
||||||
|
base = Base
|
||||||
|
}.
|
||||||
|
|
||||||
|
live_upgrade({mqueue, StoreQos0, MaxLen, Len, Dropped, PTable, DefaultP, Q}) ->
|
||||||
|
ShiftOpts = case is_map(PTable) of
|
||||||
|
true -> get_shift_opt(#{p_table => PTable});
|
||||||
|
false -> get_shift_opt(#{})
|
||||||
|
end,
|
||||||
|
#mqueue{ store_qos0 = StoreQos0
|
||||||
|
, max_len = MaxLen
|
||||||
|
, dropped = Dropped
|
||||||
|
, p_table = PTable
|
||||||
|
, default_p = DefaultP
|
||||||
|
, len = Len
|
||||||
|
, q = Q
|
||||||
|
, shift_opts = ShiftOpts
|
||||||
|
, last_p = undefined
|
||||||
|
, counter = undefined
|
||||||
|
}.
|
||||||
|
|
|
@ -81,7 +81,7 @@ get_mem_check_interval() ->
|
||||||
|
|
||||||
set_mem_check_interval(Seconds) when Seconds < 60 ->
|
set_mem_check_interval(Seconds) when Seconds < 60 ->
|
||||||
memsup:set_check_interval(1);
|
memsup:set_check_interval(1);
|
||||||
set_mem_check_interval(Seconds) ->
|
set_mem_check_interval(Seconds) ->
|
||||||
memsup:set_check_interval(Seconds div 60).
|
memsup:set_check_interval(Seconds div 60).
|
||||||
|
|
||||||
get_sysmem_high_watermark() ->
|
get_sysmem_high_watermark() ->
|
||||||
|
@ -105,8 +105,10 @@ call(Req) ->
|
||||||
|
|
||||||
init([Opts]) ->
|
init([Opts]) ->
|
||||||
set_mem_check_interval(proplists:get_value(mem_check_interval, Opts)),
|
set_mem_check_interval(proplists:get_value(mem_check_interval, Opts)),
|
||||||
set_sysmem_high_watermark(proplists:get_value(sysmem_high_watermark, Opts)),
|
HW = proplists:get_value(sysmem_high_watermark, Opts),
|
||||||
|
set_sysmem_high_watermark(HW),
|
||||||
set_procmem_high_watermark(proplists:get_value(procmem_high_watermark, Opts)),
|
set_procmem_high_watermark(proplists:get_value(procmem_high_watermark, Opts)),
|
||||||
|
ensure_system_memory_alarm(HW),
|
||||||
{ok, ensure_check_timer(#{cpu_high_watermark => proplists:get_value(cpu_high_watermark, Opts),
|
{ok, ensure_check_timer(#{cpu_high_watermark => proplists:get_value(cpu_high_watermark, Opts),
|
||||||
cpu_low_watermark => proplists:get_value(cpu_low_watermark, Opts),
|
cpu_low_watermark => proplists:get_value(cpu_low_watermark, Opts),
|
||||||
cpu_check_interval => proplists:get_value(cpu_check_interval, Opts),
|
cpu_check_interval => proplists:get_value(cpu_check_interval, Opts),
|
||||||
|
@ -177,3 +179,20 @@ ensure_check_timer(State = #{cpu_check_interval := Interval}) ->
|
||||||
"x86_64-pc-linux-musl" -> State;
|
"x86_64-pc-linux-musl" -> State;
|
||||||
_ -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}
|
_ -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% At startup, memsup starts first and checks for memory alarms,
|
||||||
|
%% but emqx_alarm_handler is not yet used instead of alarm_handler,
|
||||||
|
%% so alarm_handler is used directly for notification (normally emqx_alarm_handler should be used).
|
||||||
|
%%The internal memsup will no longer trigger events that have been alerted,
|
||||||
|
%% and there is no exported function to remove the alerted flag,
|
||||||
|
%% so it can only be checked again at startup.
|
||||||
|
ensure_system_memory_alarm(HW) ->
|
||||||
|
case erlang:whereis(memsup) of
|
||||||
|
undefined -> ok;
|
||||||
|
_Pid ->
|
||||||
|
{Allocated, Total, _Worst} = memsup:get_memory_data(),
|
||||||
|
case Total =/= 0 andalso Allocated/Total * 100 >= HW of
|
||||||
|
true -> emqx_alarm:activate(high_system_memory_usage, #{high_watermark => HW});
|
||||||
|
false -> ok
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
, filter/2
|
, filter/2
|
||||||
, fold/3
|
, fold/3
|
||||||
, highest/1
|
, highest/1
|
||||||
|
, shift/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export_type([q/0]).
|
-export_type([q/0]).
|
||||||
|
@ -170,6 +171,14 @@ out({pqueue, [{P, Q} | Queues]}) ->
|
||||||
end,
|
end,
|
||||||
{R, NewQ}.
|
{R, NewQ}.
|
||||||
|
|
||||||
|
-spec(shift(pqueue()) -> pqueue()).
|
||||||
|
shift(Q = {queue, _, _, _}) ->
|
||||||
|
Q;
|
||||||
|
shift({pqueue, []}) ->
|
||||||
|
{pqueue, []}; %% Shouldn't happen?
|
||||||
|
shift({pqueue, [Hd|Rest]}) ->
|
||||||
|
{pqueue, Rest ++ [Hd]}. %% Let's hope there are not many priorities.
|
||||||
|
|
||||||
-spec(out_p(pqueue()) -> {empty | {value, any(), priority()}, pqueue()}).
|
-spec(out_p(pqueue()) -> {empty | {value, any(), priority()}, pqueue()}).
|
||||||
out_p({queue, _, _, _} = Q) -> add_p(out(Q), 0);
|
out_p({queue, _, _, _} = Q) -> add_p(out(Q), 0);
|
||||||
out_p({pqueue, [{P, _} | _]} = Q) -> add_p(out(Q), maybe_negate_priority(P)).
|
out_p({pqueue, [{P, _} | _]} = Q) -> add_p(out(Q), maybe_negate_priority(P)).
|
||||||
|
@ -266,4 +275,3 @@ r2f([X,Y|R], L) -> {queue, [X,Y], lists:reverse(R, []), L}.
|
||||||
|
|
||||||
maybe_negate_priority(infinity) -> infinity;
|
maybe_negate_priority(infinity) -> infinity;
|
||||||
maybe_negate_priority(P) -> -P.
|
maybe_negate_priority(P) -> -P.
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,8 @@ do_add_route(Topic, Dest) when is_binary(Topic) ->
|
||||||
false ->
|
false ->
|
||||||
ok = emqx_router_helper:monitor(Dest),
|
ok = emqx_router_helper:monitor(Dest),
|
||||||
case emqx_topic:wildcard(Topic) of
|
case emqx_topic:wildcard(Topic) of
|
||||||
true -> trans(fun insert_trie_route/1, [Route]);
|
true ->
|
||||||
|
maybe_trans(fun insert_trie_route/1, [Route]);
|
||||||
false -> insert_direct_route(Route)
|
false -> insert_direct_route(Route)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
@ -164,7 +165,8 @@ do_delete_route(Topic) when is_binary(Topic) ->
|
||||||
do_delete_route(Topic, Dest) ->
|
do_delete_route(Topic, Dest) ->
|
||||||
Route = #route{topic = Topic, dest = Dest},
|
Route = #route{topic = Topic, dest = Dest},
|
||||||
case emqx_topic:wildcard(Topic) of
|
case emqx_topic:wildcard(Topic) of
|
||||||
true -> trans(fun delete_trie_route/1, [Route]);
|
true ->
|
||||||
|
maybe_trans(fun delete_trie_route/1, [Route]);
|
||||||
false -> delete_direct_route(Route)
|
false -> delete_direct_route(Route)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -247,10 +249,59 @@ delete_trie_route(Route = #route{topic = Topic}) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
-spec(trans(function(), list(any())) -> ok | {error, term()}).
|
-spec(maybe_trans(function(), list(any())) -> ok | {error, term()}).
|
||||||
trans(Fun, Args) ->
|
maybe_trans(Fun, Args) ->
|
||||||
case mnesia:transaction(Fun, Args) of
|
case persistent_term:get(emqx_route_lock_type, key) of
|
||||||
{atomic, Ok} -> Ok;
|
key ->
|
||||||
{aborted, Reason} -> {error, Reason}
|
trans(Fun, Args);
|
||||||
|
global ->
|
||||||
|
lock_router(),
|
||||||
|
try mnesia:sync_dirty(Fun, Args)
|
||||||
|
after
|
||||||
|
unlock_router()
|
||||||
|
end;
|
||||||
|
tab ->
|
||||||
|
trans(fun() ->
|
||||||
|
emqx_trie:lock_tables(),
|
||||||
|
apply(Fun, Args)
|
||||||
|
end, [])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec(trans(function(), list(any())) -> ok | {error, term()}).
|
||||||
|
trans(Fun, Args) ->
|
||||||
|
%% trigger selective receive optimization of compiler,
|
||||||
|
%% ideal for handling bursty traffic.
|
||||||
|
Ref = erlang:make_ref(),
|
||||||
|
Owner = self(),
|
||||||
|
{WPid, RefMon} = spawn_monitor(
|
||||||
|
fun() ->
|
||||||
|
Res = case mnesia:transaction(Fun, Args) of
|
||||||
|
{atomic, Ok} -> Ok;
|
||||||
|
{aborted, Reason} -> {error, Reason}
|
||||||
|
end,
|
||||||
|
Owner ! {Ref, Res}
|
||||||
|
end),
|
||||||
|
receive
|
||||||
|
{Ref, TransRes} ->
|
||||||
|
receive
|
||||||
|
{'DOWN', RefMon, process, WPid, normal} -> ok
|
||||||
|
end,
|
||||||
|
TransRes;
|
||||||
|
{'DOWN', RefMon, process, WPid, Info} ->
|
||||||
|
{error, {trans_crash, Info}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
lock_router() ->
|
||||||
|
%% if Retry is not 0, global:set_lock could sleep a random time up to 8s.
|
||||||
|
%% Considering we have a limited number of brokers, it is safe to use sleep 1 ms.
|
||||||
|
case global:set_lock({?MODULE, self()}, [node() | nodes()], 0) of
|
||||||
|
false ->
|
||||||
|
%% Force to sleep 1ms instead.
|
||||||
|
timer:sleep(1),
|
||||||
|
lock_router();
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
unlock_router() ->
|
||||||
|
global:del_lock({?MODULE, self()}).
|
||||||
|
|
|
@ -34,6 +34,10 @@ init([]) ->
|
||||||
type => worker,
|
type => worker,
|
||||||
modules => [emqx_router_helper]},
|
modules => [emqx_router_helper]},
|
||||||
|
|
||||||
|
ok = persistent_term:put(emqx_route_lock_type,
|
||||||
|
application:get_env(emqx, route_lock_type, key)
|
||||||
|
),
|
||||||
|
|
||||||
%% Router pool
|
%% Router pool
|
||||||
RouterPool = emqx_pool_sup:spec([router_pool, hash,
|
RouterPool = emqx_pool_sup:spec([router_pool, hash,
|
||||||
{emqx_router, start_link, []}]),
|
{emqx_router, start_link, []}]),
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
|
|
||||||
-export([ deliver/2
|
-export([ deliver/2
|
||||||
, enqueue/2
|
, enqueue/2
|
||||||
|
, dequeue/1
|
||||||
, retry/1
|
, retry/1
|
||||||
, terminate/3
|
, terminate/3
|
||||||
]).
|
]).
|
||||||
|
|
|
@ -31,7 +31,9 @@
|
||||||
, delete/1
|
, delete/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([empty/0]).
|
-export([ empty/0
|
||||||
|
, lock_tables/0
|
||||||
|
]).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
|
@ -120,6 +122,11 @@ delete(Topic) when is_binary(Topic) ->
|
||||||
empty() ->
|
empty() ->
|
||||||
ets:info(?TRIE_TAB, size) == 0.
|
ets:info(?TRIE_TAB, size) == 0.
|
||||||
|
|
||||||
|
-spec lock_tables() -> ok.
|
||||||
|
lock_tables() ->
|
||||||
|
mnesia:write_lock_table(?TRIE_TAB),
|
||||||
|
mnesia:write_lock_table(?TRIE_NODE_TAB).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
, stats/1
|
, stats/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([call/2]).
|
-export([call/2, call/3]).
|
||||||
|
|
||||||
%% WebSocket callbacks
|
%% WebSocket callbacks
|
||||||
-export([ init/2
|
-export([ init/2
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
%% Simulate the active_n opt
|
%% Simulate the active_n opt
|
||||||
active_n :: pos_integer(),
|
active_n :: pos_integer(),
|
||||||
%% MQTT Piggyback
|
%% MQTT Piggyback
|
||||||
mqtt_piggyback :: single | multiple,
|
mqtt_piggyback :: single | multiple,
|
||||||
%% Limiter
|
%% Limiter
|
||||||
limiter :: maybe(emqx_limiter:limiter()),
|
limiter :: maybe(emqx_limiter:limiter()),
|
||||||
%% Limit Timer
|
%% Limit Timer
|
||||||
|
@ -151,7 +151,10 @@ stats(#state{channel = Channel}) ->
|
||||||
|
|
||||||
%% kick|discard|takeover
|
%% kick|discard|takeover
|
||||||
-spec(call(pid(), Req :: term()) -> Reply :: term()).
|
-spec(call(pid(), Req :: term()) -> Reply :: term()).
|
||||||
call(WsPid, Req) when is_pid(WsPid) ->
|
call(WsPid, Req) ->
|
||||||
|
call(WsPid, Req, 5000).
|
||||||
|
|
||||||
|
call(WsPid, Req, Timeout) when is_pid(WsPid) ->
|
||||||
Mref = erlang:monitor(process, WsPid),
|
Mref = erlang:monitor(process, WsPid),
|
||||||
WsPid ! {call, {self(), Mref}, Req},
|
WsPid ! {call, {self(), Mref}, Req},
|
||||||
receive
|
receive
|
||||||
|
@ -160,7 +163,7 @@ call(WsPid, Req) when is_pid(WsPid) ->
|
||||||
Reply;
|
Reply;
|
||||||
{'DOWN', Mref, _, _, Reason} ->
|
{'DOWN', Mref, _, _, Reason} ->
|
||||||
exit(Reason)
|
exit(Reason)
|
||||||
after 5000 ->
|
after Timeout ->
|
||||||
erlang:demonitor(Mref, [flush]),
|
erlang:demonitor(Mref, [flush]),
|
||||||
exit(timeout)
|
exit(timeout)
|
||||||
end.
|
end.
|
||||||
|
@ -196,15 +199,21 @@ init(Req, Opts) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
websocket_init([Req, Opts]) ->
|
websocket_init([Req, Opts]) ->
|
||||||
Peername = case proplists:get_bool(proxy_protocol, Opts)
|
{Peername, Peercert} =
|
||||||
andalso maps:get(proxy_header, Req) of
|
case proplists:get_bool(proxy_protocol, Opts)
|
||||||
#{src_address := SrcAddr, src_port := SrcPort} ->
|
andalso maps:get(proxy_header, Req) of
|
||||||
{SrcAddr, SrcPort};
|
#{src_address := SrcAddr, src_port := SrcPort, ssl := SSL} ->
|
||||||
_ ->
|
ProxyName = {SrcAddr, SrcPort},
|
||||||
cowboy_req:peer(Req)
|
%% Notice: Only CN is available in Proxy Protocol V2 additional info
|
||||||
end,
|
ProxySSL = case maps:get(cn, SSL, undefined) of
|
||||||
|
undeined -> nossl;
|
||||||
|
CN -> [{pp2_ssl_cn, CN}]
|
||||||
|
end,
|
||||||
|
{ProxyName, ProxySSL};
|
||||||
|
_ ->
|
||||||
|
{cowboy_req:peer(Req), cowboy_req:cert(Req)}
|
||||||
|
end,
|
||||||
Sockname = cowboy_req:sock(Req),
|
Sockname = cowboy_req:sock(Req),
|
||||||
Peercert = cowboy_req:cert(Req),
|
|
||||||
WsCookie = try cowboy_req:parse_cookies(Req)
|
WsCookie = try cowboy_req:parse_cookies(Req)
|
||||||
catch
|
catch
|
||||||
error:badarg ->
|
error:badarg ->
|
||||||
|
@ -477,6 +486,12 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) ->
|
||||||
NState = State#state{parse_state = NParseState},
|
NState = State#state{parse_state = NParseState},
|
||||||
parse_incoming(Rest, postpone({incoming, Packet}, NState))
|
parse_incoming(Rest, postpone({incoming, Packet}, NState))
|
||||||
catch
|
catch
|
||||||
|
error:proxy_protocol_config_disabled ->
|
||||||
|
?LOG(error,
|
||||||
|
"~nMalformed packet, "
|
||||||
|
"please check proxy_protocol config for specific listeners and zones~n"),
|
||||||
|
FrameError = {frame_error, proxy_protocol_config_disabled},
|
||||||
|
postpone({incoming, FrameError} ,State);
|
||||||
error:Reason:Stk ->
|
error:Reason:Stk ->
|
||||||
?LOG(error, "~nParse failed for ~0p~n~0p~nFrame data: ~0p",
|
?LOG(error, "~nParse failed for ~0p~n~0p~nFrame data: ~0p",
|
||||||
[Reason, Stk, Data]),
|
[Reason, Stk, Data]),
|
||||||
|
@ -535,7 +550,7 @@ handle_outgoing(Packets, State = #state{active_n = ActiveN, mqtt_piggyback = MQT
|
||||||
postpone({check_gc, Stats}, State);
|
postpone({check_gc, Stats}, State);
|
||||||
false -> State
|
false -> State
|
||||||
end,
|
end,
|
||||||
|
|
||||||
{case MQTTPiggyback of
|
{case MQTTPiggyback of
|
||||||
single -> [{binary, IoData}];
|
single -> [{binary, IoData}];
|
||||||
multiple -> lists:map(fun(Bin) -> {binary, Bin} end, IoData)
|
multiple -> lists:map(fun(Bin) -> {binary, Bin} end, IoData)
|
||||||
|
@ -680,4 +695,3 @@ trigger(Event) -> erlang:send(self(), Event).
|
||||||
set_field(Name, Value, State) ->
|
set_field(Name, Value, State) ->
|
||||||
Pos = emqx_misc:index_of(Name, record_info(fields, state)),
|
Pos = emqx_misc:index_of(Name, record_info(fields, state)),
|
||||||
setelement(Pos+1, State, Value).
|
setelement(Pos+1, State, Value).
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ t_chan_caps(_) ->
|
||||||
#{max_clientid_len := 65535,
|
#{max_clientid_len := 65535,
|
||||||
max_qos_allowed := 2,
|
max_qos_allowed := 2,
|
||||||
max_topic_alias := 65535,
|
max_topic_alias := 65535,
|
||||||
max_topic_levels := 0,
|
max_topic_levels := 128,
|
||||||
retain_available := true,
|
retain_available := true,
|
||||||
shared_subscription := true,
|
shared_subscription := true,
|
||||||
subscription_identifiers := true,
|
subscription_identifiers := true,
|
||||||
|
@ -768,4 +768,3 @@ session(InitFields) when is_map(InitFields) ->
|
||||||
quota() ->
|
quota() ->
|
||||||
emqx_limiter:init(zone, [{conn_messages_routing, {5, 1}},
|
emqx_limiter:init(zone, [{conn_messages_routing, {5, 1}},
|
||||||
{overall_messages_routing, {10, 1}}]).
|
{overall_messages_routing, {10, 1}}]).
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,12 @@
|
||||||
conn_mod => emqx_connection,
|
conn_mod => emqx_connection,
|
||||||
receive_maximum => 100}}).
|
receive_maximum => 100}}).
|
||||||
|
|
||||||
|
-define(WAIT(PATTERN, TIMEOUT, RET),
|
||||||
|
fun() ->
|
||||||
|
receive PATTERN -> RET
|
||||||
|
after TIMEOUT -> error({timeout, ?LINE}) end
|
||||||
|
end()).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% CT callbacks
|
%% CT callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -77,7 +83,7 @@ t_get_set_chan_stats(_) ->
|
||||||
|
|
||||||
t_open_session(_) ->
|
t_open_session(_) ->
|
||||||
ok = meck:new(emqx_connection, [passthrough, no_history]),
|
ok = meck:new(emqx_connection, [passthrough, no_history]),
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end),
|
ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end),
|
||||||
|
|
||||||
ClientInfo = #{zone => external,
|
ClientInfo = #{zone => external,
|
||||||
clientid => <<"clientid">>,
|
clientid => <<"clientid">>,
|
||||||
|
@ -151,19 +157,98 @@ t_open_session_race_condition(_) ->
|
||||||
exit(Pid, kill), timer:sleep(100),
|
exit(Pid, kill), timer:sleep(100),
|
||||||
?assertEqual([], emqx_cm:lookup_channels(<<"clientid">>)).
|
?assertEqual([], emqx_cm:lookup_channels(<<"clientid">>)).
|
||||||
|
|
||||||
t_discard_session(_) ->
|
t_kick_session_discard_normal(_) ->
|
||||||
ok = meck:new(emqx_connection, [passthrough, no_history]),
|
test_kick_session(discard, normal).
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end),
|
|
||||||
ok = emqx_cm:discard_session(<<"clientid">>),
|
t_kick_session_discard_shutdown(_) ->
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, ?ChanInfo, []),
|
test_kick_session(discard, shutdown).
|
||||||
ok = emqx_cm:discard_session(<<"clientid">>),
|
|
||||||
ok = emqx_cm:unregister_channel(<<"clientid">>),
|
t_kick_session_discard_shutdown_with_reason(_) ->
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, ?ChanInfo, []),
|
test_kick_session(discard, {shutdown, discard}).
|
||||||
ok = emqx_cm:discard_session(<<"clientid">>),
|
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> error(testing) end),
|
t_kick_session_discard_timeout(_) ->
|
||||||
ok = emqx_cm:discard_session(<<"clientid">>),
|
test_kick_session(discard, timeout).
|
||||||
ok = emqx_cm:unregister_channel(<<"clientid">>),
|
|
||||||
ok = meck:unload(emqx_connection).
|
t_kick_session_discard_noproc(_) ->
|
||||||
|
test_kick_session(discard, noproc).
|
||||||
|
|
||||||
|
t_kick_session_kick_normal(_) ->
|
||||||
|
test_kick_session(discard, normal).
|
||||||
|
|
||||||
|
t_kick_session_kick_shutdown(_) ->
|
||||||
|
test_kick_session(discard, shutdown).
|
||||||
|
|
||||||
|
t_kick_session_kick_shutdown_with_reason(_) ->
|
||||||
|
test_kick_session(discard, {shutdown, discard}).
|
||||||
|
|
||||||
|
t_kick_session_kick_timeout(_) ->
|
||||||
|
test_kick_session(discard, timeout).
|
||||||
|
|
||||||
|
t_kick_session_kick_noproc(_) ->
|
||||||
|
test_kick_session(discard, noproc).
|
||||||
|
|
||||||
|
test_kick_session(Action, Reason) ->
|
||||||
|
ClientId = rand_client_id(),
|
||||||
|
#{conninfo := ConnInfo} = ?ChanInfo,
|
||||||
|
FakeSessionFun =
|
||||||
|
fun Loop() ->
|
||||||
|
receive
|
||||||
|
{'$gen_call', From, A} when A =:= kick orelse
|
||||||
|
A =:= discard ->
|
||||||
|
case Reason of
|
||||||
|
normal ->
|
||||||
|
gen_server:reply(From, ok);
|
||||||
|
timeout ->
|
||||||
|
%% no response to the call
|
||||||
|
Loop();
|
||||||
|
_ ->
|
||||||
|
exit(Reason)
|
||||||
|
end;
|
||||||
|
Msg ->
|
||||||
|
ct:pal("(~p) fake_session_discarded ~p", [Action, Msg]),
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
{Pid1, _} = spawn_monitor(FakeSessionFun),
|
||||||
|
{Pid2, _} = spawn_monitor(FakeSessionFun),
|
||||||
|
ok = emqx_cm:register_channel_(ClientId, Pid1, ConnInfo),
|
||||||
|
ok = emqx_cm:register_channel_(ClientId, Pid1, ConnInfo),
|
||||||
|
ok = emqx_cm:register_channel_(ClientId, Pid2, ConnInfo),
|
||||||
|
?assertEqual([Pid1, Pid2], lists:sort(emqx_cm:lookup_channels(ClientId))),
|
||||||
|
case Reason of
|
||||||
|
noproc -> exit(Pid1, kill), exit(Pid2, kill);
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
ok = case Action of
|
||||||
|
kick -> emqx_cm:kick_session(ClientId);
|
||||||
|
discard -> emqx_cm:discard_session(ClientId)
|
||||||
|
end,
|
||||||
|
case Reason =:= timeout orelse Reason =:= noproc of
|
||||||
|
true ->
|
||||||
|
?assertEqual(killed, ?WAIT({'DOWN', _, process, Pid1, R}, 2000, R)),
|
||||||
|
?assertEqual(killed, ?WAIT({'DOWN', _, process, Pid2, R}, 2000, R));
|
||||||
|
false ->
|
||||||
|
?assertEqual(Reason, ?WAIT({'DOWN', _, process, Pid1, R}, 2000, R)),
|
||||||
|
?assertEqual(Reason, ?WAIT({'DOWN', _, process, Pid2, R}, 2000, R))
|
||||||
|
end,
|
||||||
|
ok = flush_emqx_pool(),
|
||||||
|
?assertEqual([], emqx_cm:lookup_channels(ClientId)).
|
||||||
|
|
||||||
|
rand_client_id() ->
|
||||||
|
list_to_binary("client-id-" ++ integer_to_list(erlang:system_time())).
|
||||||
|
|
||||||
|
%% Channel deregistration is delegated to emqx_pool as a sync tasks.
|
||||||
|
%% The emqx_pool is pool of workers, and there is no way to know
|
||||||
|
%% which worker was picked for the last deregistration task.
|
||||||
|
%% This help function creates a large enough number of async tasks
|
||||||
|
%% to sync with the pool workers.
|
||||||
|
%% The number of tasks should be large enough to ensure all workers have
|
||||||
|
%% the chance to work on at least one of the tasks.
|
||||||
|
flush_emqx_pool() ->
|
||||||
|
Self = self(),
|
||||||
|
L = lists:seq(1, 1000),
|
||||||
|
lists:foreach(fun(I) -> emqx_pool:async_submit(fun() -> Self ! {done, I} end, []) end, L),
|
||||||
|
lists:foreach(fun(I) -> receive {done, I} -> ok end end, L).
|
||||||
|
|
||||||
t_takeover_session(_) ->
|
t_takeover_session(_) ->
|
||||||
{error, not_found} = emqx_cm:takeover_session(<<"clientid">>),
|
{error, not_found} = emqx_cm:takeover_session(<<"clientid">>),
|
||||||
|
@ -178,21 +263,6 @@ t_takeover_session(_) ->
|
||||||
{ok, emqx_connection, _, test} = emqx_cm:takeover_session(<<"clientid">>),
|
{ok, emqx_connection, _, test} = emqx_cm:takeover_session(<<"clientid">>),
|
||||||
emqx_cm:unregister_channel(<<"clientid">>).
|
emqx_cm:unregister_channel(<<"clientid">>).
|
||||||
|
|
||||||
t_kick_session(_) ->
|
|
||||||
ok = meck:new(emqx_connection, [passthrough, no_history]),
|
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> test end),
|
|
||||||
{error, not_found} = emqx_cm:kick_session(<<"clientid">>),
|
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, ?ChanInfo, []),
|
|
||||||
test = emqx_cm:kick_session(<<"clientid">>),
|
|
||||||
erlang:spawn(fun() ->
|
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, ?ChanInfo, []),
|
|
||||||
timer:sleep(1000)
|
|
||||||
end),
|
|
||||||
ct:sleep(100),
|
|
||||||
test = emqx_cm:kick_session(<<"clientid">>),
|
|
||||||
ok = emqx_cm:unregister_channel(<<"clientid">>),
|
|
||||||
ok = meck:unload(emqx_connection).
|
|
||||||
|
|
||||||
t_all_channels(_) ->
|
t_all_channels(_) ->
|
||||||
?assertEqual(true, is_list(emqx_cm:all_channels())).
|
?assertEqual(true, is_list(emqx_cm:all_channels())).
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,11 @@ all() ->
|
||||||
groups() ->
|
groups() ->
|
||||||
[{parse, [parallel],
|
[{parse, [parallel],
|
||||||
[t_parse_cont,
|
[t_parse_cont,
|
||||||
t_parse_frame_too_large
|
t_parse_frame_too_large,
|
||||||
|
t_parse_frame_malformed_variable_byte_integer,
|
||||||
|
t_parse_frame_variable_byte_integer,
|
||||||
|
t_parse_malformed_utf8_string,
|
||||||
|
t_parse_frame_proxy_protocol %% proxy_protocol_config_disabled packet.
|
||||||
]},
|
]},
|
||||||
{connect, [parallel],
|
{connect, [parallel],
|
||||||
[t_serialize_parse_v3_connect,
|
[t_serialize_parse_v3_connect,
|
||||||
|
@ -129,6 +133,41 @@ t_parse_frame_too_large(_) ->
|
||||||
?catch_error(frame_too_large, parse_serialize(Packet, #{max_size => 512})),
|
?catch_error(frame_too_large, parse_serialize(Packet, #{max_size => 512})),
|
||||||
?assertEqual(Packet, parse_serialize(Packet, #{max_size => 2048, version => ?MQTT_PROTO_V4})).
|
?assertEqual(Packet, parse_serialize(Packet, #{max_size => 2048, version => ?MQTT_PROTO_V4})).
|
||||||
|
|
||||||
|
t_parse_frame_malformed_variable_byte_integer(_) ->
|
||||||
|
MalformedPayload = << <<16#80>> || _ <- lists:seq(1, 4) >>,
|
||||||
|
ParseState = emqx_frame:initial_parse_state(#{}),
|
||||||
|
?catch_error(malformed_variable_byte_integer,
|
||||||
|
emqx_frame:parse(MalformedPayload, ParseState)).
|
||||||
|
|
||||||
|
t_parse_frame_variable_byte_integer(_) ->
|
||||||
|
Bin = <<2#10010011, 2#10000000, 2#10001000, 2#10011001, 2#10101101, 2#00110010>>,
|
||||||
|
?catch_error(malformed_variable_byte_integer,
|
||||||
|
emqx_frame:parse_variable_byte_integer(Bin)).
|
||||||
|
|
||||||
|
t_parse_malformed_utf8_string(_) ->
|
||||||
|
MalformedPacket = <<16,31,0,4,
|
||||||
|
%% Specification name, should be "MQTT"
|
||||||
|
%% 77,81,84,84,
|
||||||
|
%% malformed 1-Byte UTF-8 in (U+0000 .. U+001F] && [U+007F])
|
||||||
|
16#00,16#01,16#1F,16#7F,
|
||||||
|
|
||||||
|
4,194,0,60,
|
||||||
|
0,4,101,109,
|
||||||
|
113,120,0,5,
|
||||||
|
97,100,109,105,
|
||||||
|
110,0,6,112,
|
||||||
|
117,98,108,105,
|
||||||
|
99>>,
|
||||||
|
ParseState = emqx_frame:initial_parse_state(#{strict_mode => true}),
|
||||||
|
?catch_error(utf8_string_invalid, emqx_frame:parse(MalformedPacket, ParseState)).
|
||||||
|
|
||||||
|
t_parse_frame_proxy_protocol(_) ->
|
||||||
|
BinList = [ <<"PROXY TCP4 ">>, <<"PROXY TCP6 ">>, <<"PROXY UNKNOWN">>
|
||||||
|
, <<"\r\n\r\n\0\r\nQUIT\n">>],
|
||||||
|
[?assertError( proxy_protocol_config_disabled
|
||||||
|
, emqx_frame:parse(Bin))
|
||||||
|
|| Bin <- BinList].
|
||||||
|
|
||||||
t_serialize_parse_v3_connect(_) ->
|
t_serialize_parse_v3_connect(_) ->
|
||||||
Bin = <<16,37,0,6,77,81,73,115,100,112,3,2,0,60,0,23,109,111,115,
|
Bin = <<16,37,0,6,77,81,73,115,100,112,3,2,0,60,0,23,109,111,115,
|
||||||
113,112,117, 98,47,49,48,52,53,49,45,105,77,97,99,46,108,
|
113,112,117, 98,47,49,48,52,53,49,45,105,77,97,99,46,108,
|
||||||
|
@ -524,4 +563,3 @@ parse_to_packet(Bin, Opts) ->
|
||||||
Packet.
|
Packet.
|
||||||
|
|
||||||
payload(Len) -> iolist_to_binary(lists:duplicate(Len, 1)).
|
payload(Len) -> iolist_to_binary(lists:duplicate(Len, 1)).
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,23 @@
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
emqx_ct_helpers:boot_modules(all),
|
||||||
|
emqx_ct_helpers:start_apps([],
|
||||||
|
fun(emqx) ->
|
||||||
|
application:set_env(emqx, os_mon, [
|
||||||
|
{cpu_check_interval, 1},
|
||||||
|
{cpu_high_watermark, 5},
|
||||||
|
{cpu_low_watermark, 80},
|
||||||
|
{mem_check_interval, 60},
|
||||||
|
{sysmem_high_watermark, 70},
|
||||||
|
{procmem_high_watermark, 5}]);
|
||||||
|
(_) -> ok
|
||||||
|
end),
|
||||||
application:ensure_all_started(os_mon),
|
application:ensure_all_started(os_mon),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
|
emqx_ct_helpers:stop_apps([]),
|
||||||
application:stop(os_mon).
|
application:stop(os_mon).
|
||||||
|
|
||||||
% t_set_mem_check_interval(_) ->
|
% t_set_mem_check_interval(_) ->
|
||||||
|
@ -40,13 +53,6 @@ end_per_suite(_Config) ->
|
||||||
% error('TODO').
|
% error('TODO').
|
||||||
|
|
||||||
t_api(_) ->
|
t_api(_) ->
|
||||||
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
|
||||||
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1},
|
|
||||||
{cpu_high_watermark, 5},
|
|
||||||
{cpu_low_watermark, 80},
|
|
||||||
{mem_check_interval, 60},
|
|
||||||
{sysmem_high_watermark, 70},
|
|
||||||
{procmem_high_watermark, 5}]),
|
|
||||||
?assertEqual(1, emqx_os_mon:get_cpu_check_interval()),
|
?assertEqual(1, emqx_os_mon:get_cpu_check_interval()),
|
||||||
?assertEqual(5, emqx_os_mon:get_cpu_high_watermark()),
|
?assertEqual(5, emqx_os_mon:get_cpu_high_watermark()),
|
||||||
?assertEqual(80, emqx_os_mon:get_cpu_low_watermark()),
|
?assertEqual(80, emqx_os_mon:get_cpu_low_watermark()),
|
||||||
|
@ -69,4 +75,3 @@ t_api(_) ->
|
||||||
emqx_os_mon ! ignored,
|
emqx_os_mon ! ignored,
|
||||||
gen_server:stop(emqx_os_mon),
|
gen_server:stop(emqx_os_mon),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
|
@ -270,7 +270,7 @@ t_connect_limit_timeout(_) ->
|
||||||
meck:unload(proplists).
|
meck:unload(proplists).
|
||||||
|
|
||||||
t_connect_emit_stats_timeout(_) ->
|
t_connect_emit_stats_timeout(_) ->
|
||||||
IdleTimeout = 2000,
|
IdleTimeout = 2000 + 200,
|
||||||
emqx_zone:set_env(external, idle_timeout, IdleTimeout),
|
emqx_zone:set_env(external, idle_timeout, IdleTimeout),
|
||||||
|
|
||||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
|
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
|
||||||
|
@ -278,7 +278,7 @@ t_connect_emit_stats_timeout(_) ->
|
||||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
||||||
|
|
||||||
?assert(is_reference(emqx_connection:info(stats_timer, sys:get_state(ClientPid)))),
|
?assert(is_reference(emqx_connection:info(stats_timer, sys:get_state(ClientPid)))),
|
||||||
timer:sleep(IdleTimeout),
|
timer:sleep(IdleTimeout+500),
|
||||||
?assertEqual(undefined, emqx_connection:info(stats_timer, sys:get_state(ClientPid))),
|
?assertEqual(undefined, emqx_connection:info(stats_timer, sys:get_state(ClientPid))),
|
||||||
ok = emqtt:disconnect(Client).
|
ok = emqtt:disconnect(Client).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue