chore: format code apps/emqx

This commit is contained in:
Zaiming (Stone) Shi 2022-03-21 16:51:31 +01:00
parent 80b4313f00
commit 91bcf02970
181 changed files with 19749 additions and 14190 deletions

View File

@ -25,11 +25,12 @@
-define(ROUTE_SHARD, route_shard). -define(ROUTE_SHARD, route_shard).
-define(PERSISTENT_SESSION_SHARD, emqx_persistent_session_shard). -define(PERSISTENT_SESSION_SHARD, emqx_persistent_session_shard).
-define(BOOT_SHARDS, [ ?ROUTE_SHARD -define(BOOT_SHARDS, [
, ?COMMON_SHARD ?ROUTE_SHARD,
, ?SHARED_SUB_SHARD ?COMMON_SHARD,
, ?PERSISTENT_SESSION_SHARD ?SHARED_SUB_SHARD,
]). ?PERSISTENT_SESSION_SHARD
]).
%% Banner %% Banner
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -63,96 +64,99 @@
%% See 'Application Message' in MQTT Version 5.0 %% See 'Application Message' in MQTT Version 5.0
-record(message, { -record(message, {
%% Global unique message ID %% Global unique message ID
id :: binary(), id :: binary(),
%% Message QoS %% Message QoS
qos = 0, qos = 0,
%% Message from %% Message from
from :: atom() | binary(), from :: atom() | binary(),
%% Message flags %% Message flags
flags = #{} :: emqx_types:flags(), flags = #{} :: emqx_types:flags(),
%% Message headers. May contain any metadata. e.g. the %% Message headers. May contain any metadata. e.g. the
%% protocol version number, username, peerhost or %% protocol version number, username, peerhost or
%% the PUBLISH properties (MQTT 5.0). %% the PUBLISH properties (MQTT 5.0).
headers = #{} :: emqx_types:headers(), headers = #{} :: emqx_types:headers(),
%% Topic that the message is published to %% Topic that the message is published to
topic :: emqx_types:topic(), topic :: emqx_types:topic(),
%% Message Payload %% Message Payload
payload :: emqx_types:payload(), payload :: emqx_types:payload(),
%% Timestamp (Unit: millisecond) %% Timestamp (Unit: millisecond)
timestamp :: integer(), timestamp :: integer(),
%% not used so far, for future extension %% not used so far, for future extension
extra = [] :: term() extra = [] :: term()
}). }).
-record(delivery, { -record(delivery, {
sender :: pid(), %% Sender of the delivery %% Sender of the delivery
message :: #message{} %% The message delivered sender :: pid(),
}). %% The message delivered
message :: #message{}
}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Route %% Route
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(route, { -record(route, {
topic :: binary(), topic :: binary(),
dest :: node() | {binary(), node()} | emqx_session:sessionID() dest :: node() | {binary(), node()} | emqx_session:sessionID()
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Plugin %% Plugin
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(plugin, { -record(plugin, {
name :: atom(), name :: atom(),
dir :: string() | undefined, dir :: string() | undefined,
descr :: string(), descr :: string(),
vendor :: string() | undefined, vendor :: string() | undefined,
active = false :: boolean(), active = false :: boolean(),
info = #{} :: map() info = #{} :: map()
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Command %% Command
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(command, { -record(command, {
name :: atom(), name :: atom(),
action :: atom(), action :: atom(),
args = [] :: list(), args = [] :: list(),
opts = [] :: list(), opts = [] :: list(),
usage :: string(), usage :: string(),
descr :: string() descr :: string()
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Banned %% Banned
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(banned, { -record(banned, {
who :: {clientid, binary()} who ::
| {peerhost, inet:ip_address()} {clientid, binary()}
| {username, binary()}, | {peerhost, inet:ip_address()}
by :: binary(), | {username, binary()},
reason :: binary(), by :: binary(),
at :: integer(), reason :: binary(),
until :: integer() at :: integer(),
}). until :: integer()
}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Authentication %% Authentication
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(authenticator, -record(authenticator, {
{ id :: binary() id :: binary(),
, provider :: module() provider :: module(),
, enable :: boolean() enable :: boolean(),
, state :: map() state :: map()
}). }).
-record(chain, -record(chain, {
{ name :: atom() name :: atom(),
, authenticators :: [#authenticator{}] authenticators :: [#authenticator{}]
}). }).
-endif. -endif.

View File

@ -23,8 +23,13 @@
%% MQTT SockOpts %% MQTT SockOpts
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(MQTT_SOCKOPTS, [binary, {packet, raw}, {reuseaddr, true}, -define(MQTT_SOCKOPTS, [
{backlog, 512}, {nodelay, true}]). binary,
{packet, raw},
{reuseaddr, true},
{backlog, 512},
{nodelay, true}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Protocol Version and Names %% MQTT Protocol Version and Names
@ -36,40 +41,45 @@
-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 %% XXX:Compatible with emqx-sn plug-in
{?MQTT_SN_PROTO_V1, <<"MQTT-SN">>},
{?MQTT_PROTO_V3, <<"MQIsdp">>}, {?MQTT_PROTO_V3, <<"MQIsdp">>},
{?MQTT_PROTO_V4, <<"MQTT">>}, {?MQTT_PROTO_V4, <<"MQTT">>},
{?MQTT_PROTO_V5, <<"MQTT">>}]). {?MQTT_PROTO_V5, <<"MQTT">>}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT QoS Levels %% MQTT QoS Levels
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(QOS_0, 0). %% At most once %% At most once
-define(QOS_1, 1). %% At least once -define(QOS_0, 0).
-define(QOS_2, 2). %% Exactly once %% At least once
-define(QOS_1, 1).
%% Exactly once
-define(QOS_2, 2).
-define(IS_QOS(I), (I >= ?QOS_0 andalso I =< ?QOS_2)). -define(IS_QOS(I), (I >= ?QOS_0 andalso I =< ?QOS_2)).
-define(QOS_I(Name), -define(QOS_I(Name), begin
begin (case Name of
(case Name of ?QOS_0 -> ?QOS_0;
?QOS_0 -> ?QOS_0; qos0 -> ?QOS_0;
qos0 -> ?QOS_0; at_most_once -> ?QOS_0;
at_most_once -> ?QOS_0; ?QOS_1 -> ?QOS_1;
?QOS_1 -> ?QOS_1; qos1 -> ?QOS_1;
qos1 -> ?QOS_1; at_least_once -> ?QOS_1;
at_least_once -> ?QOS_1; ?QOS_2 -> ?QOS_2;
?QOS_2 -> ?QOS_2; qos2 -> ?QOS_2;
qos2 -> ?QOS_2; exactly_once -> ?QOS_2
exactly_once -> ?QOS_2 end)
end) end).
end).
-define(IS_QOS_NAME(I), -define(IS_QOS_NAME(I),
(I =:= qos0 orelse I =:= at_most_once orelse (I =:= qos0 orelse I =:= at_most_once orelse
I =:= qos1 orelse I =:= at_least_once orelse I =:= qos1 orelse I =:= at_least_once orelse
I =:= qos2 orelse I =:= exactly_once)). I =:= qos2 orelse I =:= exactly_once)
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Maximum ClientId Length. %% Maximum ClientId Length.
@ -81,83 +91,105 @@
%% MQTT Control Packet Types %% MQTT Control Packet Types
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(RESERVED, 0). %% Reserved %% Reserved
-define(CONNECT, 1). %% Client request to connect to Server -define(RESERVED, 0).
-define(CONNACK, 2). %% Server to Client: Connect acknowledgment %% Client request to connect to Server
-define(PUBLISH, 3). %% Publish message -define(CONNECT, 1).
-define(PUBACK, 4). %% Publish acknowledgment %% Server to Client: Connect acknowledgment
-define(PUBREC, 5). %% Publish received (assured delivery part 1) -define(CONNACK, 2).
-define(PUBREL, 6). %% Publish release (assured delivery part 2) %% Publish message
-define(PUBCOMP, 7). %% Publish complete (assured delivery part 3) -define(PUBLISH, 3).
-define(SUBSCRIBE, 8). %% Client subscribe request %% Publish acknowledgment
-define(SUBACK, 9). %% Server Subscribe acknowledgment -define(PUBACK, 4).
-define(UNSUBSCRIBE, 10). %% Unsubscribe request %% Publish received (assured delivery part 1)
-define(UNSUBACK, 11). %% Unsubscribe acknowledgment -define(PUBREC, 5).
-define(PINGREQ, 12). %% PING request %% Publish release (assured delivery part 2)
-define(PINGRESP, 13). %% PING response -define(PUBREL, 6).
-define(DISCONNECT, 14). %% Client or Server is disconnecting %% Publish complete (assured delivery part 3)
-define(AUTH, 15). %% Authentication exchange -define(PUBCOMP, 7).
%% Client subscribe request
-define(SUBSCRIBE, 8).
%% Server Subscribe acknowledgment
-define(SUBACK, 9).
%% Unsubscribe request
-define(UNSUBSCRIBE, 10).
%% Unsubscribe acknowledgment
-define(UNSUBACK, 11).
%% PING request
-define(PINGREQ, 12).
%% PING response
-define(PINGRESP, 13).
%% Client or Server is disconnecting
-define(DISCONNECT, 14).
%% Authentication exchange
-define(AUTH, 15).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT V3.1.1 Connect Return Codes %% MQTT V3.1.1 Connect Return Codes
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(CONNACK_ACCEPT, 0). %% Connection accepted %% Connection accepted
-define(CONNACK_PROTO_VER, 1). %% Unacceptable protocol version -define(CONNACK_ACCEPT, 0).
-define(CONNACK_INVALID_ID, 2). %% Client Identifier is correct UTF-8 but not allowed by the Server %% Unacceptable protocol version
-define(CONNACK_SERVER, 3). %% Server unavailable -define(CONNACK_PROTO_VER, 1).
-define(CONNACK_CREDENTIALS, 4). %% Username or password is malformed %% Client Identifier is correct UTF-8 but not allowed by the Server
-define(CONNACK_AUTH, 5). %% Client is not authorized to connect -define(CONNACK_INVALID_ID, 2).
%% Server unavailable
-define(CONNACK_SERVER, 3).
%% Username or password is malformed
-define(CONNACK_CREDENTIALS, 4).
%% Client is not authorized to connect
-define(CONNACK_AUTH, 5).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT V5.0 Reason Codes %% MQTT V5.0 Reason Codes
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(RC_SUCCESS, 16#00). -define(RC_SUCCESS, 16#00).
-define(RC_NORMAL_DISCONNECTION, 16#00). -define(RC_NORMAL_DISCONNECTION, 16#00).
-define(RC_GRANTED_QOS_0, 16#00). -define(RC_GRANTED_QOS_0, 16#00).
-define(RC_GRANTED_QOS_1, 16#01). -define(RC_GRANTED_QOS_1, 16#01).
-define(RC_GRANTED_QOS_2, 16#02). -define(RC_GRANTED_QOS_2, 16#02).
-define(RC_DISCONNECT_WITH_WILL_MESSAGE, 16#04). -define(RC_DISCONNECT_WITH_WILL_MESSAGE, 16#04).
-define(RC_NO_MATCHING_SUBSCRIBERS, 16#10). -define(RC_NO_MATCHING_SUBSCRIBERS, 16#10).
-define(RC_NO_SUBSCRIPTION_EXISTED, 16#11). -define(RC_NO_SUBSCRIPTION_EXISTED, 16#11).
-define(RC_CONTINUE_AUTHENTICATION, 16#18). -define(RC_CONTINUE_AUTHENTICATION, 16#18).
-define(RC_RE_AUTHENTICATE, 16#19). -define(RC_RE_AUTHENTICATE, 16#19).
-define(RC_UNSPECIFIED_ERROR, 16#80). -define(RC_UNSPECIFIED_ERROR, 16#80).
-define(RC_MALFORMED_PACKET, 16#81). -define(RC_MALFORMED_PACKET, 16#81).
-define(RC_PROTOCOL_ERROR, 16#82). -define(RC_PROTOCOL_ERROR, 16#82).
-define(RC_IMPLEMENTATION_SPECIFIC_ERROR, 16#83). -define(RC_IMPLEMENTATION_SPECIFIC_ERROR, 16#83).
-define(RC_UNSUPPORTED_PROTOCOL_VERSION, 16#84). -define(RC_UNSUPPORTED_PROTOCOL_VERSION, 16#84).
-define(RC_CLIENT_IDENTIFIER_NOT_VALID, 16#85). -define(RC_CLIENT_IDENTIFIER_NOT_VALID, 16#85).
-define(RC_BAD_USER_NAME_OR_PASSWORD, 16#86). -define(RC_BAD_USER_NAME_OR_PASSWORD, 16#86).
-define(RC_NOT_AUTHORIZED, 16#87). -define(RC_NOT_AUTHORIZED, 16#87).
-define(RC_SERVER_UNAVAILABLE, 16#88). -define(RC_SERVER_UNAVAILABLE, 16#88).
-define(RC_SERVER_BUSY, 16#89). -define(RC_SERVER_BUSY, 16#89).
-define(RC_BANNED, 16#8A). -define(RC_BANNED, 16#8A).
-define(RC_SERVER_SHUTTING_DOWN, 16#8B). -define(RC_SERVER_SHUTTING_DOWN, 16#8B).
-define(RC_BAD_AUTHENTICATION_METHOD, 16#8C). -define(RC_BAD_AUTHENTICATION_METHOD, 16#8C).
-define(RC_KEEP_ALIVE_TIMEOUT, 16#8D). -define(RC_KEEP_ALIVE_TIMEOUT, 16#8D).
-define(RC_SESSION_TAKEN_OVER, 16#8E). -define(RC_SESSION_TAKEN_OVER, 16#8E).
-define(RC_TOPIC_FILTER_INVALID, 16#8F). -define(RC_TOPIC_FILTER_INVALID, 16#8F).
-define(RC_TOPIC_NAME_INVALID, 16#90). -define(RC_TOPIC_NAME_INVALID, 16#90).
-define(RC_PACKET_IDENTIFIER_IN_USE, 16#91). -define(RC_PACKET_IDENTIFIER_IN_USE, 16#91).
-define(RC_PACKET_IDENTIFIER_NOT_FOUND, 16#92). -define(RC_PACKET_IDENTIFIER_NOT_FOUND, 16#92).
-define(RC_RECEIVE_MAXIMUM_EXCEEDED, 16#93). -define(RC_RECEIVE_MAXIMUM_EXCEEDED, 16#93).
-define(RC_TOPIC_ALIAS_INVALID, 16#94). -define(RC_TOPIC_ALIAS_INVALID, 16#94).
-define(RC_PACKET_TOO_LARGE, 16#95). -define(RC_PACKET_TOO_LARGE, 16#95).
-define(RC_MESSAGE_RATE_TOO_HIGH, 16#96). -define(RC_MESSAGE_RATE_TOO_HIGH, 16#96).
-define(RC_QUOTA_EXCEEDED, 16#97). -define(RC_QUOTA_EXCEEDED, 16#97).
-define(RC_ADMINISTRATIVE_ACTION, 16#98). -define(RC_ADMINISTRATIVE_ACTION, 16#98).
-define(RC_PAYLOAD_FORMAT_INVALID, 16#99). -define(RC_PAYLOAD_FORMAT_INVALID, 16#99).
-define(RC_RETAIN_NOT_SUPPORTED, 16#9A). -define(RC_RETAIN_NOT_SUPPORTED, 16#9A).
-define(RC_QOS_NOT_SUPPORTED, 16#9B). -define(RC_QOS_NOT_SUPPORTED, 16#9B).
-define(RC_USE_ANOTHER_SERVER, 16#9C). -define(RC_USE_ANOTHER_SERVER, 16#9C).
-define(RC_SERVER_MOVED, 16#9D). -define(RC_SERVER_MOVED, 16#9D).
-define(RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, 16#9E). -define(RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, 16#9E).
-define(RC_CONNECTION_RATE_EXCEEDED, 16#9F). -define(RC_CONNECTION_RATE_EXCEEDED, 16#9F).
-define(RC_MAXIMUM_CONNECT_TIME, 16#A0). -define(RC_MAXIMUM_CONNECT_TIME, 16#A0).
-define(RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED, 16#A1). -define(RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED, 16#A1).
-define(RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, 16#A2). -define(RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, 16#A2).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Maximum MQTT Packet ID and Length %% Maximum MQTT Packet ID and Length
@ -180,367 +212,455 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(mqtt_packet_header, { -record(mqtt_packet_header, {
type = ?RESERVED, type = ?RESERVED,
dup = false, dup = false,
qos = ?QOS_0, qos = ?QOS_0,
retain = false retain = false
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Packets %% MQTT Packets
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(DEFAULT_SUBOPTS, #{rh => 0, %% Retain Handling %% Retain Handling
rap => 0, %% Retain as Publish -define(DEFAULT_SUBOPTS, #{
nl => 0, %% No Local rh => 0,
qos => 0 %% QoS %% Retain as Publish
}). rap => 0,
%% No Local
nl => 0,
%% QoS
qos => 0
}).
-record(mqtt_packet_connect, { -record(mqtt_packet_connect, {
proto_name = <<"MQTT">>, proto_name = <<"MQTT">>,
proto_ver = ?MQTT_PROTO_V4, proto_ver = ?MQTT_PROTO_V4,
is_bridge = false, is_bridge = false,
clean_start = true, clean_start = true,
will_flag = false, will_flag = false,
will_qos = ?QOS_0, will_qos = ?QOS_0,
will_retain = false, will_retain = false,
keepalive = 0, keepalive = 0,
properties = #{}, properties = #{},
clientid = <<>>, clientid = <<>>,
will_props = #{}, will_props = #{},
will_topic = undefined, will_topic = undefined,
will_payload = undefined, will_payload = undefined,
username = undefined, username = undefined,
password = undefined password = undefined
}). }).
-record(mqtt_packet_connack, { -record(mqtt_packet_connack, {
ack_flags, ack_flags,
reason_code, reason_code,
properties = #{} properties = #{}
}). }).
-record(mqtt_packet_publish, { -record(mqtt_packet_publish, {
topic_name, topic_name,
packet_id, packet_id,
properties = #{} properties = #{}
}). }).
-record(mqtt_packet_puback, { -record(mqtt_packet_puback, {
packet_id, packet_id,
reason_code, reason_code,
properties = #{} properties = #{}
}). }).
-record(mqtt_packet_subscribe, { -record(mqtt_packet_subscribe, {
packet_id, packet_id,
properties = #{}, properties = #{},
topic_filters topic_filters
}). }).
-record(mqtt_packet_suback, { -record(mqtt_packet_suback, {
packet_id, packet_id,
properties = #{}, properties = #{},
reason_codes reason_codes
}). }).
-record(mqtt_packet_unsubscribe, { -record(mqtt_packet_unsubscribe, {
packet_id, packet_id,
properties = #{}, properties = #{},
topic_filters topic_filters
}). }).
-record(mqtt_packet_unsuback, { -record(mqtt_packet_unsuback, {
packet_id, packet_id,
properties = #{}, properties = #{},
reason_codes reason_codes
}). }).
-record(mqtt_packet_disconnect, { -record(mqtt_packet_disconnect, {
reason_code, reason_code,
properties = #{} properties = #{}
}). }).
-record(mqtt_packet_auth, { -record(mqtt_packet_auth, {
reason_code, reason_code,
properties = #{} properties = #{}
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Control Packet %% MQTT Control Packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(mqtt_packet, { -record(mqtt_packet, {
header :: #mqtt_packet_header{}, header :: #mqtt_packet_header{},
variable :: #mqtt_packet_connect{} variable ::
| #mqtt_packet_connack{} #mqtt_packet_connect{}
| #mqtt_packet_publish{} | #mqtt_packet_connack{}
| #mqtt_packet_puback{} | #mqtt_packet_publish{}
| #mqtt_packet_subscribe{} | #mqtt_packet_puback{}
| #mqtt_packet_suback{} | #mqtt_packet_subscribe{}
| #mqtt_packet_unsubscribe{} | #mqtt_packet_suback{}
| #mqtt_packet_unsuback{} | #mqtt_packet_unsubscribe{}
| #mqtt_packet_disconnect{} | #mqtt_packet_unsuback{}
| #mqtt_packet_auth{} | #mqtt_packet_disconnect{}
| pos_integer() | #mqtt_packet_auth{}
| undefined, | pos_integer()
payload :: binary() | undefined | undefined,
}). payload :: binary() | undefined
}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Message Internal %% MQTT Message Internal
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(mqtt_msg, { -record(mqtt_msg, {
qos = ?QOS_0, qos = ?QOS_0,
retain = false, retain = false,
dup = false, dup = false,
packet_id, packet_id,
topic, topic,
props, props,
payload payload
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Packet Match %% MQTT Packet Match
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(CONNECT_PACKET(), -define(CONNECT_PACKET(), #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}}).
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}}).
-define(CONNECT_PACKET(Var), -define(CONNECT_PACKET(Var), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, header = #mqtt_packet_header{type = ?CONNECT},
variable = Var}). variable = Var
}).
-define(CONNACK_PACKET(ReasonCode), -define(CONNACK_PACKET(ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, header = #mqtt_packet_header{type = ?CONNACK},
variable = #mqtt_packet_connack{ack_flags = 0, variable = #mqtt_packet_connack{
reason_code = ReasonCode} ack_flags = 0,
}). reason_code = ReasonCode
}
}).
-define(CONNACK_PACKET(ReasonCode, SessPresent), -define(CONNACK_PACKET(ReasonCode, SessPresent), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, header = #mqtt_packet_header{type = ?CONNACK},
variable = #mqtt_packet_connack{ack_flags = SessPresent, variable = #mqtt_packet_connack{
reason_code = ReasonCode} ack_flags = SessPresent,
}). reason_code = ReasonCode
}
}).
-define(CONNACK_PACKET(ReasonCode, SessPresent, Properties), -define(CONNACK_PACKET(ReasonCode, SessPresent, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, header = #mqtt_packet_header{type = ?CONNACK},
variable = #mqtt_packet_connack{ack_flags = SessPresent, variable = #mqtt_packet_connack{
reason_code = ReasonCode, ack_flags = SessPresent,
properties = Properties} reason_code = ReasonCode,
}). properties = Properties
}
}).
-define(AUTH_PACKET(), -define(AUTH_PACKET(), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?AUTH}, header = #mqtt_packet_header{type = ?AUTH},
variable = #mqtt_packet_auth{reason_code = 0} variable = #mqtt_packet_auth{reason_code = 0}
}). }).
-define(AUTH_PACKET(ReasonCode), -define(AUTH_PACKET(ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?AUTH}, header = #mqtt_packet_header{type = ?AUTH},
variable = #mqtt_packet_auth{reason_code = ReasonCode} variable = #mqtt_packet_auth{reason_code = ReasonCode}
}). }).
-define(AUTH_PACKET(ReasonCode, Properties), -define(AUTH_PACKET(ReasonCode, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?AUTH}, header = #mqtt_packet_header{type = ?AUTH},
variable = #mqtt_packet_auth{reason_code = ReasonCode, variable = #mqtt_packet_auth{
properties = Properties} reason_code = ReasonCode,
}). properties = Properties
}
}).
-define(PUBLISH_PACKET(QoS), -define(PUBLISH_PACKET(QoS), #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, qos = QoS}}).
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, qos = QoS}}).
-define(PUBLISH_PACKET(QoS, PacketId), -define(PUBLISH_PACKET(QoS, PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, header = #mqtt_packet_header{
qos = QoS}, type = ?PUBLISH,
variable = #mqtt_packet_publish{packet_id = PacketId} qos = QoS
}). },
variable = #mqtt_packet_publish{packet_id = PacketId}
}).
-define(PUBLISH_PACKET(QoS, Topic, PacketId), -define(PUBLISH_PACKET(QoS, Topic, PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, header = #mqtt_packet_header{
qos = QoS}, type = ?PUBLISH,
variable = #mqtt_packet_publish{topic_name = Topic, qos = QoS
packet_id = PacketId} },
}). variable = #mqtt_packet_publish{
topic_name = Topic,
packet_id = PacketId
}
}).
-define(PUBLISH_PACKET(QoS, Topic, PacketId, Payload), -define(PUBLISH_PACKET(QoS, Topic, PacketId, Payload), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, header = #mqtt_packet_header{
qos = QoS}, type = ?PUBLISH,
variable = #mqtt_packet_publish{topic_name = Topic, qos = QoS
packet_id = PacketId}, },
payload = Payload variable = #mqtt_packet_publish{
}). topic_name = Topic,
packet_id = PacketId
},
payload = Payload
}).
-define(PUBLISH_PACKET(QoS, Topic, PacketId, Properties, Payload), -define(PUBLISH_PACKET(QoS, Topic, PacketId, Properties, Payload), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, header = #mqtt_packet_header{
qos = QoS}, type = ?PUBLISH,
variable = #mqtt_packet_publish{topic_name = Topic, qos = QoS
packet_id = PacketId, },
properties = Properties}, variable = #mqtt_packet_publish{
payload = Payload topic_name = Topic,
}). packet_id = PacketId,
properties = Properties
},
payload = Payload
}).
-define(PUBACK_PACKET(PacketId), -define(PUBACK_PACKET(PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBACK}, header = #mqtt_packet_header{type = ?PUBACK},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = 0} packet_id = PacketId,
}). reason_code = 0
}
}).
-define(PUBACK_PACKET(PacketId, ReasonCode), -define(PUBACK_PACKET(PacketId, ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBACK}, header = #mqtt_packet_header{type = ?PUBACK},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = ReasonCode} packet_id = PacketId,
}). reason_code = ReasonCode
}
}).
-define(PUBACK_PACKET(PacketId, ReasonCode, Properties), -define(PUBACK_PACKET(PacketId, ReasonCode, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBACK}, header = #mqtt_packet_header{type = ?PUBACK},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = ReasonCode, packet_id = PacketId,
properties = Properties} reason_code = ReasonCode,
}). properties = Properties
}
}).
-define(PUBREC_PACKET(PacketId), -define(PUBREC_PACKET(PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBREC}, header = #mqtt_packet_header{type = ?PUBREC},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = 0} packet_id = PacketId,
}). reason_code = 0
}
}).
-define(PUBREC_PACKET(PacketId, ReasonCode), -define(PUBREC_PACKET(PacketId, ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBREC}, header = #mqtt_packet_header{type = ?PUBREC},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = ReasonCode} packet_id = PacketId,
}). reason_code = ReasonCode
}
}).
-define(PUBREC_PACKET(PacketId, ReasonCode, Properties), -define(PUBREC_PACKET(PacketId, ReasonCode, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBREC}, header = #mqtt_packet_header{type = ?PUBREC},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = ReasonCode, packet_id = PacketId,
properties = Properties} reason_code = ReasonCode,
}). properties = Properties
}
}).
-define(PUBREL_PACKET(PacketId), -define(PUBREL_PACKET(PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBREL, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?PUBREL,
variable = #mqtt_packet_puback{packet_id = PacketId, qos = ?QOS_1
reason_code = 0} },
}). variable = #mqtt_packet_puback{
packet_id = PacketId,
reason_code = 0
}
}).
-define(PUBREL_PACKET(PacketId, ReasonCode), -define(PUBREL_PACKET(PacketId, ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBREL, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?PUBREL,
variable = #mqtt_packet_puback{packet_id = PacketId, qos = ?QOS_1
reason_code = ReasonCode} },
}). variable = #mqtt_packet_puback{
packet_id = PacketId,
reason_code = ReasonCode
}
}).
-define(PUBREL_PACKET(PacketId, ReasonCode, Properties), -define(PUBREL_PACKET(PacketId, ReasonCode, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBREL, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?PUBREL,
variable = #mqtt_packet_puback{packet_id = PacketId, qos = ?QOS_1
reason_code = ReasonCode, },
properties = Properties} variable = #mqtt_packet_puback{
}). packet_id = PacketId,
reason_code = ReasonCode,
properties = Properties
}
}).
-define(PUBCOMP_PACKET(PacketId), -define(PUBCOMP_PACKET(PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBCOMP}, header = #mqtt_packet_header{type = ?PUBCOMP},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = 0} packet_id = PacketId,
}). reason_code = 0
}
}).
-define(PUBCOMP_PACKET(PacketId, ReasonCode), -define(PUBCOMP_PACKET(PacketId, ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBCOMP}, header = #mqtt_packet_header{type = ?PUBCOMP},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = ReasonCode} packet_id = PacketId,
}). reason_code = ReasonCode
}
}).
-define(PUBCOMP_PACKET(PacketId, ReasonCode, Properties), -define(PUBCOMP_PACKET(PacketId, ReasonCode, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBCOMP}, header = #mqtt_packet_header{type = ?PUBCOMP},
variable = #mqtt_packet_puback{packet_id = PacketId, variable = #mqtt_packet_puback{
reason_code = ReasonCode, packet_id = PacketId,
properties = Properties} reason_code = ReasonCode,
}). properties = Properties
}
}).
-define(SUBSCRIBE_PACKET(PacketId, TopicFilters), -define(SUBSCRIBE_PACKET(PacketId, TopicFilters), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?SUBSCRIBE, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?SUBSCRIBE,
variable = #mqtt_packet_subscribe{packet_id = PacketId, qos = ?QOS_1
topic_filters = TopicFilters} },
}). variable = #mqtt_packet_subscribe{
packet_id = PacketId,
topic_filters = TopicFilters
}
}).
-define(SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), -define(SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?SUBSCRIBE, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?SUBSCRIBE,
variable = #mqtt_packet_subscribe{packet_id = PacketId, qos = ?QOS_1
properties = Properties, },
topic_filters = TopicFilters} variable = #mqtt_packet_subscribe{
}). packet_id = PacketId,
properties = Properties,
topic_filters = TopicFilters
}
}).
-define(SUBACK_PACKET(PacketId, ReasonCodes), -define(SUBACK_PACKET(PacketId, ReasonCodes), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?SUBACK}, header = #mqtt_packet_header{type = ?SUBACK},
variable = #mqtt_packet_suback{packet_id = PacketId, variable = #mqtt_packet_suback{
reason_codes = ReasonCodes} packet_id = PacketId,
}). reason_codes = ReasonCodes
}
}).
-define(SUBACK_PACKET(PacketId, Properties, ReasonCodes), -define(SUBACK_PACKET(PacketId, Properties, ReasonCodes), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?SUBACK}, header = #mqtt_packet_header{type = ?SUBACK},
variable = #mqtt_packet_suback{packet_id = PacketId, variable = #mqtt_packet_suback{
properties = Properties, packet_id = PacketId,
reason_codes = ReasonCodes} properties = Properties,
}). reason_codes = ReasonCodes
}
}).
-define(UNSUBSCRIBE_PACKET(PacketId, TopicFilters), -define(UNSUBSCRIBE_PACKET(PacketId, TopicFilters), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?UNSUBSCRIBE, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?UNSUBSCRIBE,
variable = #mqtt_packet_unsubscribe{packet_id = PacketId, qos = ?QOS_1
topic_filters = TopicFilters} },
}). variable = #mqtt_packet_unsubscribe{
packet_id = PacketId,
topic_filters = TopicFilters
}
}).
-define(UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), -define(UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?UNSUBSCRIBE, header = #mqtt_packet_header{
qos = ?QOS_1}, type = ?UNSUBSCRIBE,
variable = #mqtt_packet_unsubscribe{packet_id = PacketId, qos = ?QOS_1
properties = Properties, },
topic_filters = TopicFilters} variable = #mqtt_packet_unsubscribe{
}). packet_id = PacketId,
properties = Properties,
topic_filters = TopicFilters
}
}).
-define(UNSUBACK_PACKET(PacketId), -define(UNSUBACK_PACKET(PacketId), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?UNSUBACK}, header = #mqtt_packet_header{type = ?UNSUBACK},
variable = #mqtt_packet_unsuback{packet_id = PacketId} variable = #mqtt_packet_unsuback{packet_id = PacketId}
}). }).
-define(UNSUBACK_PACKET(PacketId, ReasonCodes), -define(UNSUBACK_PACKET(PacketId, ReasonCodes), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?UNSUBACK}, header = #mqtt_packet_header{type = ?UNSUBACK},
variable = #mqtt_packet_unsuback{packet_id = PacketId, variable = #mqtt_packet_unsuback{
reason_codes = ReasonCodes} packet_id = PacketId,
}). reason_codes = ReasonCodes
}
}).
-define(UNSUBACK_PACKET(PacketId, Properties, ReasonCodes), -define(UNSUBACK_PACKET(PacketId, Properties, ReasonCodes), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?UNSUBACK}, header = #mqtt_packet_header{type = ?UNSUBACK},
variable = #mqtt_packet_unsuback{packet_id = PacketId, variable = #mqtt_packet_unsuback{
properties = Properties, packet_id = PacketId,
reason_codes = ReasonCodes} properties = Properties,
}). reason_codes = ReasonCodes
}
}).
-define(DISCONNECT_PACKET(), -define(DISCONNECT_PACKET(), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?DISCONNECT}, header = #mqtt_packet_header{type = ?DISCONNECT},
variable = #mqtt_packet_disconnect{reason_code = 0} variable = #mqtt_packet_disconnect{reason_code = 0}
}). }).
-define(DISCONNECT_PACKET(ReasonCode), -define(DISCONNECT_PACKET(ReasonCode), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?DISCONNECT}, header = #mqtt_packet_header{type = ?DISCONNECT},
variable = #mqtt_packet_disconnect{reason_code = ReasonCode} variable = #mqtt_packet_disconnect{reason_code = ReasonCode}
}). }).
-define(DISCONNECT_PACKET(ReasonCode, Properties), -define(DISCONNECT_PACKET(ReasonCode, Properties), #mqtt_packet{
#mqtt_packet{header = #mqtt_packet_header{type = ?DISCONNECT}, header = #mqtt_packet_header{type = ?DISCONNECT},
variable = #mqtt_packet_disconnect{reason_code = ReasonCode, variable = #mqtt_packet_disconnect{
properties = Properties} reason_code = ReasonCode,
}). properties = Properties
}
}).
-define(PACKET(Type), #mqtt_packet{header = #mqtt_packet_header{type = Type}}). -define(PACKET(Type), #mqtt_packet{header = #mqtt_packet_header{type = Type}}).
-define(SHARE, "$share"). -define(SHARE, "$share").
-define(SHARE(Group, Topic), emqx_topic:join([<<?SHARE>>, Group, Topic])). -define(SHARE(Group, Topic), emqx_topic:join([<<?SHARE>>, Group, Topic])).
-define(IS_SHARE(Topic), case Topic of <<?SHARE, _/binary>> -> true; _ -> false end). -define(IS_SHARE(Topic),
case Topic of
<<?SHARE, _/binary>> -> true;
_ -> false
end
).
-define(FRAME_PARSE_ERROR(Reason), {frame_parse_error, Reason}). -define(FRAME_PARSE_ERROR(Reason), {frame_parse_error, Reason}).
-define(FRAME_SERIALIZE_ERROR(Reason), {frame_serialize_error, Reason}). -define(FRAME_SERIALIZE_ERROR(Reason), {frame_serialize_error, Reason}).

View File

@ -17,99 +17,99 @@
-ifndef(EMQX_PLACEHOLDER_HRL). -ifndef(EMQX_PLACEHOLDER_HRL).
-define(EMQX_PLACEHOLDER_HRL, true). -define(EMQX_PLACEHOLDER_HRL, true).
-define(PH(Type), <<"${", Type/binary, "}">> ). -define(PH(Type), <<"${", Type/binary, "}">>).
%% action: publish/subscribe/all %% action: publish/subscribe/all
-define(PH_ACTION, <<"${action}">> ). -define(PH_ACTION, <<"${action}">>).
%% cert %% cert
-define(PH_CERT_SUBJECT, <<"${cert_subject}">> ). -define(PH_CERT_SUBJECT, <<"${cert_subject}">>).
-define(PH_CERT_CN_NAME, <<"${cert_common_name}">> ). -define(PH_CERT_CN_NAME, <<"${cert_common_name}">>).
%% MQTT %% MQTT
-define(PH_PASSWORD, <<"${password}">> ). -define(PH_PASSWORD, <<"${password}">>).
-define(PH_CLIENTID, <<"${clientid}">> ). -define(PH_CLIENTID, <<"${clientid}">>).
-define(PH_FROM_CLIENTID, <<"${from_clientid}">> ). -define(PH_FROM_CLIENTID, <<"${from_clientid}">>).
-define(PH_USERNAME, <<"${username}">> ). -define(PH_USERNAME, <<"${username}">>).
-define(PH_FROM_USERNAME, <<"${from_username}">> ). -define(PH_FROM_USERNAME, <<"${from_username}">>).
-define(PH_TOPIC, <<"${topic}">> ). -define(PH_TOPIC, <<"${topic}">>).
%% MQTT payload %% MQTT payload
-define(PH_PAYLOAD, <<"${payload}">> ). -define(PH_PAYLOAD, <<"${payload}">>).
%% client IPAddress %% client IPAddress
-define(PH_PEERHOST, <<"${peerhost}">> ). -define(PH_PEERHOST, <<"${peerhost}">>).
%% ip & port %% ip & port
-define(PH_HOST, <<"${host}">> ). -define(PH_HOST, <<"${host}">>).
-define(PH_PORT, <<"${port}">> ). -define(PH_PORT, <<"${port}">>).
%% Enumeration of message QoS 0,1,2 %% Enumeration of message QoS 0,1,2
-define(PH_QOS, <<"${qos}">> ). -define(PH_QOS, <<"${qos}">>).
-define(PH_FLAGS, <<"${flags}">> ). -define(PH_FLAGS, <<"${flags}">>).
%% Additional data related to process within the MQTT message %% Additional data related to process within the MQTT message
-define(PH_HEADERS, <<"${headers}">> ). -define(PH_HEADERS, <<"${headers}">>).
%% protocol name %% protocol name
-define(PH_PROTONAME, <<"${proto_name}">> ). -define(PH_PROTONAME, <<"${proto_name}">>).
%% protocol version %% protocol version
-define(PH_PROTOVER, <<"${proto_ver}">> ). -define(PH_PROTOVER, <<"${proto_ver}">>).
%% MQTT keepalive interval %% MQTT keepalive interval
-define(PH_KEEPALIVE, <<"${keepalive}">> ). -define(PH_KEEPALIVE, <<"${keepalive}">>).
%% MQTT clean_start %% MQTT clean_start
-define(PH_CLEAR_START, <<"${clean_start}">> ). -define(PH_CLEAR_START, <<"${clean_start}">>).
%% MQTT Session Expiration time %% MQTT Session Expiration time
-define(PH_EXPIRY_INTERVAL, <<"${expiry_interval}">> ). -define(PH_EXPIRY_INTERVAL, <<"${expiry_interval}">>).
%% Time when PUBLISH message reaches Broker (ms) %% Time when PUBLISH message reaches Broker (ms)
-define(PH_PUBLISH_RECEIVED_AT, <<"${publish_received_at}">>). -define(PH_PUBLISH_RECEIVED_AT, <<"${publish_received_at}">>).
%% Mountpoint for bridging messages %% Mountpoint for bridging messages
-define(PH_MOUNTPOINT, <<"${mountpoint}">> ). -define(PH_MOUNTPOINT, <<"${mountpoint}">>).
%% IPAddress and Port of terminal %% IPAddress and Port of terminal
-define(PH_PEERNAME, <<"${peername}">> ). -define(PH_PEERNAME, <<"${peername}">>).
%% IPAddress and Port listened by emqx %% IPAddress and Port listened by emqx
-define(PH_SOCKNAME, <<"${sockname}">> ). -define(PH_SOCKNAME, <<"${sockname}">>).
%% whether it is MQTT bridge connection %% whether it is MQTT bridge connection
-define(PH_IS_BRIDGE, <<"${is_bridge}">> ). -define(PH_IS_BRIDGE, <<"${is_bridge}">>).
%% Terminal connection completion time (s) %% Terminal connection completion time (s)
-define(PH_CONNECTED_AT, <<"${connected_at}">> ). -define(PH_CONNECTED_AT, <<"${connected_at}">>).
%% Event trigger time(millisecond) %% Event trigger time(millisecond)
-define(PH_TIMESTAMP, <<"${timestamp}">> ). -define(PH_TIMESTAMP, <<"${timestamp}">>).
%% Terminal disconnection completion time (s) %% Terminal disconnection completion time (s)
-define(PH_DISCONNECTED_AT, <<"${disconnected_at}">> ). -define(PH_DISCONNECTED_AT, <<"${disconnected_at}">>).
-define(PH_NODE, <<"${node}">> ). -define(PH_NODE, <<"${node}">>).
-define(PH_REASON, <<"${reason}">> ). -define(PH_REASON, <<"${reason}">>).
-define(PH_ENDPOINT_NAME, <<"${endpoint_name}">> ). -define(PH_ENDPOINT_NAME, <<"${endpoint_name}">>).
%% sync change these place holder with binary def. %% sync change these place holder with binary def.
-define(PH_S_ACTION, "${action}" ). -define(PH_S_ACTION, "${action}").
-define(PH_S_CERT_SUBJECT, "${cert_subject}" ). -define(PH_S_CERT_SUBJECT, "${cert_subject}").
-define(PH_S_CERT_CN_NAME, "${cert_common_name}" ). -define(PH_S_CERT_CN_NAME, "${cert_common_name}").
-define(PH_S_PASSWORD, "${password}" ). -define(PH_S_PASSWORD, "${password}").
-define(PH_S_CLIENTID, "${clientid}" ). -define(PH_S_CLIENTID, "${clientid}").
-define(PH_S_FROM_CLIENTID, "${from_clientid}" ). -define(PH_S_FROM_CLIENTID, "${from_clientid}").
-define(PH_S_USERNAME, "${username}" ). -define(PH_S_USERNAME, "${username}").
-define(PH_S_FROM_USERNAME, "${from_username}" ). -define(PH_S_FROM_USERNAME, "${from_username}").
-define(PH_S_TOPIC, "${topic}" ). -define(PH_S_TOPIC, "${topic}").
-define(PH_S_PAYLOAD, "${payload}" ). -define(PH_S_PAYLOAD, "${payload}").
-define(PH_S_PEERHOST, "${peerhost}" ). -define(PH_S_PEERHOST, "${peerhost}").
-define(PH_S_HOST, "${host}" ). -define(PH_S_HOST, "${host}").
-define(PH_S_PORT, "${port}" ). -define(PH_S_PORT, "${port}").
-define(PH_S_QOS, "${qos}" ). -define(PH_S_QOS, "${qos}").
-define(PH_S_FLAGS, "${flags}" ). -define(PH_S_FLAGS, "${flags}").
-define(PH_S_HEADERS, "${headers}" ). -define(PH_S_HEADERS, "${headers}").
-define(PH_S_PROTONAME, "${proto_name}" ). -define(PH_S_PROTONAME, "${proto_name}").
-define(PH_S_PROTOVER, "${proto_ver}" ). -define(PH_S_PROTOVER, "${proto_ver}").
-define(PH_S_KEEPALIVE, "${keepalive}" ). -define(PH_S_KEEPALIVE, "${keepalive}").
-define(PH_S_CLEAR_START, "${clean_start}" ). -define(PH_S_CLEAR_START, "${clean_start}").
-define(PH_S_EXPIRY_INTERVAL, "${expiry_interval}" ). -define(PH_S_EXPIRY_INTERVAL, "${expiry_interval}").
-define(PH_S_PUBLISH_RECEIVED_AT, "${publish_received_at}" ). -define(PH_S_PUBLISH_RECEIVED_AT, "${publish_received_at}").
-define(PH_S_MOUNTPOINT, "${mountpoint}" ). -define(PH_S_MOUNTPOINT, "${mountpoint}").
-define(PH_S_PEERNAME, "${peername}" ). -define(PH_S_PEERNAME, "${peername}").
-define(PH_S_SOCKNAME, "${sockname}" ). -define(PH_S_SOCKNAME, "${sockname}").
-define(PH_S_IS_BRIDGE, "${is_bridge}" ). -define(PH_S_IS_BRIDGE, "${is_bridge}").
-define(PH_S_CONNECTED_AT, "${connected_at}" ). -define(PH_S_CONNECTED_AT, "${connected_at}").
-define(PH_S_TIMESTAMP, "${timestamp}" ). -define(PH_S_TIMESTAMP, "${timestamp}").
-define(PH_S_DISCONNECTED_AT, "${disconnected_at}" ). -define(PH_S_DISCONNECTED_AT, "${disconnected_at}").
-define(PH_S_NODE, "${node}" ). -define(PH_S_NODE, "${node}").
-define(PH_S_REASON, "${reason}" ). -define(PH_S_REASON, "${reason}").
-define(PH_S_ENDPOINT_NAME, "${endpoint_name}" ). -define(PH_S_ENDPOINT_NAME, "${endpoint_name}").
-endif. -endif.

View File

@ -15,66 +15,66 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Bad Request %% Bad Request
-define(BAD_REQUEST, 'BAD_REQUEST'). -define(BAD_REQUEST, 'BAD_REQUEST').
-define(NOT_MATCH, 'NOT_MATCH'). -define(NOT_MATCH, 'NOT_MATCH').
-define(ALREADY_EXISTS, 'ALREADY_EXISTS'). -define(ALREADY_EXISTS, 'ALREADY_EXISTS').
-define(BAD_CONFIG_SCHEMA, 'BAD_CONFIG_SCHEMA'). -define(BAD_CONFIG_SCHEMA, 'BAD_CONFIG_SCHEMA').
-define(BAD_LISTENER_ID, 'BAD_LISTENER_ID'). -define(BAD_LISTENER_ID, 'BAD_LISTENER_ID').
-define(BAD_NODE_NAME, 'BAD_NODE_NAME'). -define(BAD_NODE_NAME, 'BAD_NODE_NAME').
-define(BAD_RPC, 'BAD_RPC'). -define(BAD_RPC, 'BAD_RPC').
-define(BAD_TOPIC, 'BAD_TOPIC'). -define(BAD_TOPIC, 'BAD_TOPIC').
-define(EXCEED_LIMIT, 'EXCEED_LIMIT'). -define(EXCEED_LIMIT, 'EXCEED_LIMIT').
-define(INVALID_PARAMETER, 'INVALID_PARAMETER'). -define(INVALID_PARAMETER, 'INVALID_PARAMETER').
-define(CONFLICT, 'CONFLICT'). -define(CONFLICT, 'CONFLICT').
-define(NO_DEFAULT_VALUE, 'NO_DEFAULT_VALUE'). -define(NO_DEFAULT_VALUE, 'NO_DEFAULT_VALUE').
-define(DEPENDENCY_EXISTS, 'DEPENDENCY_EXISTS'). -define(DEPENDENCY_EXISTS, 'DEPENDENCY_EXISTS').
-define(MESSAGE_ID_SCHEMA_ERROR, 'MESSAGE_ID_SCHEMA_ERROR'). -define(MESSAGE_ID_SCHEMA_ERROR, 'MESSAGE_ID_SCHEMA_ERROR').
-define(INVALID_ID, 'INVALID_ID'). -define(INVALID_ID, 'INVALID_ID').
%% Resource Not Found %% Resource Not Found
-define(NOT_FOUND, 'NOT_FOUND'). -define(NOT_FOUND, 'NOT_FOUND').
-define(CLIENTID_NOT_FOUND, 'CLIENTID_NOT_FOUND'). -define(CLIENTID_NOT_FOUND, 'CLIENTID_NOT_FOUND').
-define(CLIENT_NOT_FOUND, 'CLIENT_NOT_FOUND'). -define(CLIENT_NOT_FOUND, 'CLIENT_NOT_FOUND').
-define(MESSAGE_ID_NOT_FOUND, 'MESSAGE_ID_NOT_FOUND'). -define(MESSAGE_ID_NOT_FOUND, 'MESSAGE_ID_NOT_FOUND').
-define(RESOURCE_NOT_FOUND, 'RESOURCE_NOT_FOUND'). -define(RESOURCE_NOT_FOUND, 'RESOURCE_NOT_FOUND').
-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND'). -define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND').
-define(USER_NOT_FOUND, 'USER_NOT_FOUND'). -define(USER_NOT_FOUND, 'USER_NOT_FOUND').
%% Internal error %% Internal error
-define(INTERNAL_ERROR, 'INTERNAL_ERROR'). -define(INTERNAL_ERROR, 'INTERNAL_ERROR').
-define(SOURCE_ERROR, 'SOURCE_ERROR'). -define(SOURCE_ERROR, 'SOURCE_ERROR').
-define(UPDATE_FAILED, 'UPDATE_FAILED'). -define(UPDATE_FAILED, 'UPDATE_FAILED').
-define(REST_FAILED, 'REST_FAILED'). -define(REST_FAILED, 'REST_FAILED').
-define(CLIENT_NOT_RESPONSE, 'CLIENT_NOT_RESPONSE'). -define(CLIENT_NOT_RESPONSE, 'CLIENT_NOT_RESPONSE').
%% All codes %% All codes
-define(ERROR_CODES, -define(ERROR_CODES, [
[ {'BAD_REQUEST', <<"Request parameters are not legal">>} {'BAD_REQUEST', <<"Request parameters are not legal">>},
, {'NOT_MATCH', <<"Conditions are not matched">>} {'NOT_MATCH', <<"Conditions are not matched">>},
, {'ALREADY_EXISTS', <<"Resource already existed">>} {'ALREADY_EXISTS', <<"Resource already existed">>},
, {'BAD_CONFIG_SCHEMA', <<"Configuration data is not legal">>} {'BAD_CONFIG_SCHEMA', <<"Configuration data is not legal">>},
, {'BAD_LISTENER_ID', <<"Bad listener ID">>} {'BAD_LISTENER_ID', <<"Bad listener ID">>},
, {'BAD_NODE_NAME', <<"Bad Node Name">>} {'BAD_NODE_NAME', <<"Bad Node Name">>},
, {'BAD_RPC', <<"RPC Failed. Check the cluster status and the requested node status">>} {'BAD_RPC', <<"RPC Failed. Check the cluster status and the requested node status">>},
, {'BAD_TOPIC', <<"Topic syntax error, Topic needs to comply with the MQTT protocol standard">>} {'BAD_TOPIC', <<"Topic syntax error, Topic needs to comply with the MQTT protocol standard">>},
, {'EXCEED_LIMIT', <<"Create resources that exceed the maximum limit or minimum limit">>} {'EXCEED_LIMIT', <<"Create resources that exceed the maximum limit or minimum limit">>},
, {'INVALID_PARAMETER', <<"Request parameters is not legal and exceeds the boundary value">>} {'INVALID_PARAMETER', <<"Request parameters is not legal and exceeds the boundary value">>},
, {'CONFLICT', <<"Conflicting request resources">>} {'CONFLICT', <<"Conflicting request resources">>},
, {'NO_DEFAULT_VALUE', <<"Request parameters do not use default values">>} {'NO_DEFAULT_VALUE', <<"Request parameters do not use default values">>},
, {'DEPENDENCY_EXISTS', <<"Resource is dependent by another resource">>} {'DEPENDENCY_EXISTS', <<"Resource is dependent by another resource">>},
, {'MESSAGE_ID_SCHEMA_ERROR', <<"Message ID parsing error">>} {'MESSAGE_ID_SCHEMA_ERROR', <<"Message ID parsing error">>},
, {'INVALID_ID', <<"Bad ID schema">>} {'INVALID_ID', <<"Bad ID schema">>},
, {'MESSAGE_ID_NOT_FOUND', <<"Message ID does not exist">>} {'MESSAGE_ID_NOT_FOUND', <<"Message ID does not exist">>},
, {'NOT_FOUND', <<"Resource was not found or does not exist">>} {'NOT_FOUND', <<"Resource was not found or does not exist">>},
, {'CLIENTID_NOT_FOUND', <<"Client ID was not found or does not exist">>} {'CLIENTID_NOT_FOUND', <<"Client ID was not found or does not exist">>},
, {'CLIENT_NOT_FOUND', <<"Client was not found or does not exist(usually not a MQTT client)">>} {'CLIENT_NOT_FOUND', <<"Client was not found or does not exist(usually not a MQTT client)">>},
, {'RESOURCE_NOT_FOUND', <<"Resource not found">>} {'RESOURCE_NOT_FOUND', <<"Resource not found">>},
, {'TOPIC_NOT_FOUND', <<"Topic not found">>} {'TOPIC_NOT_FOUND', <<"Topic not found">>},
, {'USER_NOT_FOUND', <<"User not found">>} {'USER_NOT_FOUND', <<"User not found">>},
, {'INTERNAL_ERROR', <<"Server inter error">>} {'INTERNAL_ERROR', <<"Server inter error">>},
, {'SOURCE_ERROR', <<"Source error">>} {'SOURCE_ERROR', <<"Source error">>},
, {'UPDATE_FAILED', <<"Update failed">>} {'UPDATE_FAILED', <<"Update failed">>},
, {'REST_FAILED', <<"Reset source or config failed">>} {'REST_FAILED', <<"Reset source or config failed">>},
, {'CLIENT_NOT_RESPONSE', <<"Client not responding">>} {'CLIENT_NOT_RESPONSE', <<"Client not responding">>}
]). ]).

View File

@ -19,34 +19,43 @@
%% structured logging %% structured logging
-define(SLOG(Level, Data), -define(SLOG(Level, Data),
?SLOG(Level, Data, #{})). ?SLOG(Level, Data, #{})
).
%% structured logging, meta is for handler's filter. %% structured logging, meta is for handler's filter.
-define(SLOG(Level, Data, Meta), -define(SLOG(Level, Data, Meta),
%% check 'allow' here, only evaluate Data and Meta when necessary %% check 'allow' here, only evaluate Data and Meta when necessary
case logger:allow(Level, ?MODULE) of case logger:allow(Level, ?MODULE) of
true -> true ->
logger:log(Level, (Data), (Meta#{ mfa => {?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY} logger:log(
, line => ?LINE Level,
})); (Data),
(Meta#{
mfa => {?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY},
line => ?LINE
})
);
false -> false ->
ok ok
end). end
).
-define(TRACE_FILTER, emqx_trace_filter). -define(TRACE_FILTER, emqx_trace_filter).
%% Only evaluate when necessary %% Only evaluate when necessary
%% Always debug the trace events. %% Always debug the trace events.
-define(TRACE(Tag, Msg, Meta), -define(TRACE(Tag, Msg, Meta), begin
begin
case persistent_term:get(?TRACE_FILTER, undefined) of case persistent_term:get(?TRACE_FILTER, undefined) of
undefined -> ok; undefined -> ok;
[] -> ok; [] -> ok;
List -> emqx_trace:log(List, Msg, Meta#{trace_tag => Tag}) List -> emqx_trace:log(List, Msg, Meta#{trace_tag => Tag})
end, end,
?SLOG(debug, (emqx_trace_formatter:format_meta(Meta))#{msg => Msg, tag => Tag}, ?SLOG(
#{is_trace => false}) debug,
end). (emqx_trace_formatter:format_meta(Meta))#{msg => Msg, tag => Tag},
#{is_trace => false}
)
end).
%% print to 'user' group leader %% print to 'user' group leader
-define(ULOG(Fmt, Args), io:format(user, Fmt, Args)). -define(ULOG(Fmt, Args), io:format(user, Fmt, Args)).

View File

@ -14,13 +14,12 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-type(maybe(T) :: undefined | T). -type maybe(T) :: undefined | T.
-type(startlink_ret() :: {ok, pid()} | ignore | {error, term()}). -type startlink_ret() :: {ok, pid()} | ignore | {error, term()}.
-type(ok_or_error(Reason) :: ok | {error, Reason}). -type ok_or_error(Reason) :: ok | {error, Reason}.
-type(ok_or_error(Value, Reason) :: {ok, Value} | {error, Reason}). -type ok_or_error(Value, Reason) :: {ok, Value} | {error, Reason}.
-type(mfargs() :: {module(), atom(), [term()]}).
-type mfargs() :: {module(), atom(), [term()]}.

View File

@ -1,42 +1,53 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{erl_opts, [warn_unused_vars,warn_shadow_vars,warn_unused_import, {erl_opts, [
warn_obsolete_guard,compressed]}. warn_unused_vars,
warn_shadow_vars,
warn_unused_import,
warn_obsolete_guard,
compressed
]}.
{xref_checks,[undefined_function_calls,undefined_functions,locals_not_used, {xref_checks, [
deprecated_function_calls,warnings_as_errors,deprecated_functions]}. undefined_function_calls,
undefined_functions,
locals_not_used,
deprecated_function_calls,
warnings_as_errors,
deprecated_functions
]}.
%% Deps here may duplicate with emqx.git root level rebar.config %% Deps here may duplicate with emqx.git root level rebar.config
%% but there not be any descrpancy. %% but there not be any descrpancy.
%% This rebar.config is necessary because the app may be used as a %% This rebar.config is necessary because the app may be used as a
%% `git_subdir` dependency in other projects. %% `git_subdir` dependency in other projects.
{deps, {deps, [
[ {lc, {git, "https://github.com/qzhuyan/lc.git", {tag, "0.1.2"}}} {lc, {git, "https://github.com/qzhuyan/lc.git", {tag, "0.1.2"}}},
, {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}},
, {typerefl, {git, "https://github.com/ieQu1/typerefl", {tag, "0.8.6"}}} {typerefl, {git, "https://github.com/ieQu1/typerefl", {tag, "0.8.6"}}},
, {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.9.0"}}} {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}},
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.1"}}} {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.1"}}},
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.12.2"}}} {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.12.2"}}},
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}} {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}},
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.26.3"}}} {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.26.3"}}},
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}} {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}},
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}} {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}},
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.18.0"}}} {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.18.0"}}}
]}. ]}.
{plugins, [{rebar3_proper, "0.12.1"}]}. {plugins, [{rebar3_proper, "0.12.1"}]}.
{extra_src_dirs, [{"etc", [recursive]}]}. {extra_src_dirs, [{"etc", [recursive]}]}.
{profiles, [ {profiles, [
{test, {test, [
[{deps, {deps, [
[ {meck, "0.9.2"} {meck, "0.9.2"},
, {proper, "1.4.0"} {proper, "1.4.0"},
, {bbmustache,"1.10.0"} {bbmustache, "1.10.0"},
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.4.8"}}} {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.4.8"}}}
]}, ]},
{extra_src_dirs, [{"test",[recursive]}]} {extra_src_dirs, [{"test", [recursive]}]}
]} ]}
]}. ]}.
{dialyzer, [ {dialyzer, [
@ -46,5 +57,6 @@
{plt_apps, all_apps}, {plt_apps, all_apps},
{plt_extra_apps, [hocon]}, {plt_extra_apps, [hocon]},
{statistics, true} {statistics, true}
] ]}.
}.
{project_plugins, [erlfmt]}.

View File

@ -1,28 +1,31 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx, {application, emqx, [
[{id, "emqx"}, {id, "emqx"},
{description, "EMQX Core"}, {description, "EMQX Core"},
{vsn, "5.0.0"}, % strict semver, bump manually! % strict semver, bump manually!
{modules, []}, {vsn, "5.0.0"},
{registered, []}, {modules, []},
{applications, [ kernel {registered, []},
, stdlib {applications, [
, gproc kernel,
, gen_rpc stdlib,
, mria gproc,
, esockd gen_rpc,
, cowboy mria,
, sasl esockd,
, os_mon cowboy,
, jiffy sasl,
, lc os_mon,
, hocon jiffy,
]}, lc,
{mod, {emqx_app,[]}}, hocon
{env, []}, ]},
{licenses, ["Apache-2.0"]}, {mod, {emqx_app, []}},
{maintainers, ["EMQX Team <contact@emqx.io>"]}, {env, []},
{links, [{"Homepage", "https://emqx.io/"}, {licenses, ["Apache-2.0"]},
{"Github", "https://github.com/emqx/emqx"} {maintainers, ["EMQX Team <contact@emqx.io>"]},
]} {links, [
{"Homepage", "https://emqx.io/"},
{"Github", "https://github.com/emqx/emqx"}
]}
]}. ]}.

View File

@ -23,49 +23,54 @@
-elvis([{elvis_style, god_modules, disable}]). -elvis([{elvis_style, god_modules, disable}]).
%% Start/Stop the application %% Start/Stop the application
-export([ start/0 -export([
, is_running/0 start/0,
, is_running/1 is_running/0,
, stop/0 is_running/1,
]). stop/0
]).
%% PubSub API %% PubSub API
-export([ subscribe/1 -export([
, subscribe/2 subscribe/1,
, subscribe/3 subscribe/2,
, publish/1 subscribe/3,
, unsubscribe/1 publish/1,
]). unsubscribe/1
]).
%% PubSub management API %% PubSub management API
-export([ topics/0 -export([
, subscriptions/1 topics/0,
, subscribers/1 subscriptions/1,
, subscribed/2 subscribers/1,
]). subscribed/2
]).
%% Hooks API %% Hooks API
-export([ hook/2 -export([
, hook/3 hook/2,
, hook/4 hook/3,
, unhook/2 hook/4,
, run_hook/2 unhook/2,
, run_fold_hook/3 run_hook/2,
]). run_fold_hook/3
]).
%% Configs APIs %% Configs APIs
-export([ get_config/1 -export([
, get_config/2 get_config/1,
, get_raw_config/1 get_config/2,
, get_raw_config/2 get_raw_config/1,
, update_config/2 get_raw_config/2,
, update_config/3 update_config/2,
, remove_config/1 update_config/3,
, remove_config/2 remove_config/1,
, reset_config/2 remove_config/2,
, data_dir/0 reset_config/2,
, certs_dir/0 data_dir/0,
]). certs_dir/0
]).
-define(APP, ?MODULE). -define(APP, ?MODULE).
@ -74,55 +79,58 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Start emqx application %% @doc Start emqx application
-spec(start() -> {ok, list(atom())} | {error, term()}). -spec start() -> {ok, list(atom())} | {error, term()}.
start() -> start() ->
application:ensure_all_started(?APP). application:ensure_all_started(?APP).
%% @doc Stop emqx application. %% @doc Stop emqx application.
-spec(stop() -> ok | {error, term()}). -spec stop() -> ok | {error, term()}.
stop() -> stop() ->
application:stop(?APP). application:stop(?APP).
%% @doc Is emqx running? %% @doc Is emqx running?
-spec(is_running(node()) -> boolean()). -spec is_running(node()) -> boolean().
is_running(Node) -> is_running(Node) ->
case emqx_proto_v1:is_running(Node) of case emqx_proto_v1:is_running(Node) of
{badrpc, _} -> false; {badrpc, _} -> false;
Result -> Result Result -> Result
end. end.
%% @doc Is emqx running on this node? %% @doc Is emqx running on this node?
-spec(is_running() -> boolean()). -spec is_running() -> boolean().
is_running() -> is_running() ->
case whereis(?APP) of case whereis(?APP) of
undefined -> false; undefined -> false;
_ -> true _ -> true
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% PubSub API %% PubSub API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(subscribe(emqx_types:topic() | string()) -> ok). -spec subscribe(emqx_types:topic() | string()) -> ok.
subscribe(Topic) -> subscribe(Topic) ->
emqx_broker:subscribe(iolist_to_binary(Topic)). emqx_broker:subscribe(iolist_to_binary(Topic)).
-spec(subscribe(emqx_types:topic() | string(), emqx_types:subid() | emqx_types:subopts()) -> ok). -spec subscribe(emqx_types:topic() | string(), emqx_types:subid() | emqx_types:subopts()) -> ok.
subscribe(Topic, SubId) when is_atom(SubId); is_binary(SubId)-> subscribe(Topic, SubId) when is_atom(SubId); is_binary(SubId) ->
emqx_broker:subscribe(iolist_to_binary(Topic), SubId); emqx_broker:subscribe(iolist_to_binary(Topic), SubId);
subscribe(Topic, SubOpts) when is_map(SubOpts) -> subscribe(Topic, SubOpts) when is_map(SubOpts) ->
emqx_broker:subscribe(iolist_to_binary(Topic), SubOpts). emqx_broker:subscribe(iolist_to_binary(Topic), SubOpts).
-spec(subscribe(emqx_types:topic() | string(), -spec subscribe(
emqx_types:subid() | pid(), emqx_types:subopts()) -> ok). emqx_types:topic() | string(),
emqx_types:subid() | pid(),
emqx_types:subopts()
) -> ok.
subscribe(Topic, SubId, SubOpts) when (is_atom(SubId) orelse is_binary(SubId)), is_map(SubOpts) -> subscribe(Topic, SubId, SubOpts) when (is_atom(SubId) orelse is_binary(SubId)), is_map(SubOpts) ->
emqx_broker:subscribe(iolist_to_binary(Topic), SubId, SubOpts). emqx_broker:subscribe(iolist_to_binary(Topic), SubId, SubOpts).
-spec(publish(emqx_types:message()) -> emqx_types:publish_result()). -spec publish(emqx_types:message()) -> emqx_types:publish_result().
publish(Msg) -> publish(Msg) ->
emqx_broker:publish(Msg). emqx_broker:publish(Msg).
-spec(unsubscribe(emqx_types:topic() | string()) -> ok). -spec unsubscribe(emqx_types:topic() | string()) -> ok.
unsubscribe(Topic) -> unsubscribe(Topic) ->
emqx_broker:unsubscribe(iolist_to_binary(Topic)). emqx_broker:unsubscribe(iolist_to_binary(Topic)).
@ -130,18 +138,18 @@ unsubscribe(Topic) ->
%% PubSub management API %% PubSub management API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(topics() -> list(emqx_types:topic())). -spec topics() -> list(emqx_types:topic()).
topics() -> emqx_router:topics(). topics() -> emqx_router:topics().
-spec(subscribers(emqx_types:topic() | string()) -> [pid()]). -spec subscribers(emqx_types:topic() | string()) -> [pid()].
subscribers(Topic) -> subscribers(Topic) ->
emqx_broker:subscribers(iolist_to_binary(Topic)). emqx_broker:subscribers(iolist_to_binary(Topic)).
-spec(subscriptions(pid()) -> [{emqx_types:topic(), emqx_types:subopts()}]). -spec subscriptions(pid()) -> [{emqx_types:topic(), emqx_types:subopts()}].
subscriptions(SubPid) when is_pid(SubPid) -> subscriptions(SubPid) when is_pid(SubPid) ->
emqx_broker:subscriptions(SubPid). emqx_broker:subscriptions(SubPid).
-spec(subscribed(pid() | emqx_types:subid(), emqx_types:topic() | string()) -> boolean()). -spec subscribed(pid() | emqx_types:subid(), emqx_types:topic() | string()) -> boolean().
subscribed(SubPid, Topic) when is_pid(SubPid) -> subscribed(SubPid, Topic) when is_pid(SubPid) ->
emqx_broker:subscribed(SubPid, iolist_to_binary(Topic)); emqx_broker:subscribed(SubPid, iolist_to_binary(Topic));
subscribed(SubId, Topic) when is_atom(SubId); is_binary(SubId) -> subscribed(SubId, Topic) when is_atom(SubId); is_binary(SubId) ->
@ -151,33 +159,35 @@ subscribed(SubId, Topic) when is_atom(SubId); is_binary(SubId) ->
%% Hooks API %% Hooks API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(hook(emqx_hooks:hookpoint(), emqx_hooks:action()) -> ok | {error, already_exists}). -spec hook(emqx_hooks:hookpoint(), emqx_hooks:action()) -> ok | {error, already_exists}.
hook(HookPoint, Action) -> hook(HookPoint, Action) ->
emqx_hooks:add(HookPoint, Action). emqx_hooks:add(HookPoint, Action).
-spec(hook(emqx_hooks:hookpoint(), -spec hook(
emqx_hooks:action(), emqx_hooks:hookpoint(),
emqx_hooks:filter() | integer() | list()) emqx_hooks:action(),
-> ok | {error, already_exists}). emqx_hooks:filter() | integer() | list()
) ->
ok | {error, already_exists}.
hook(HookPoint, Action, Priority) when is_integer(Priority) -> hook(HookPoint, Action, Priority) when is_integer(Priority) ->
emqx_hooks:add(HookPoint, Action, Priority); emqx_hooks:add(HookPoint, Action, Priority);
hook(HookPoint, Action, {_M, _F, _A} = Filter ) -> hook(HookPoint, Action, {_M, _F, _A} = Filter) ->
emqx_hooks:add(HookPoint, Action, Filter). emqx_hooks:add(HookPoint, Action, Filter).
-spec(hook(emqx_hooks:hookpoint(), emqx_hooks:action(), emqx_hooks:filter(), integer()) -spec hook(emqx_hooks:hookpoint(), emqx_hooks:action(), emqx_hooks:filter(), integer()) ->
-> ok | {error, already_exists}). ok | {error, already_exists}.
hook(HookPoint, Action, Filter, Priority) -> hook(HookPoint, Action, Filter, Priority) ->
emqx_hooks:add(HookPoint, Action, Filter, Priority). emqx_hooks:add(HookPoint, Action, Filter, Priority).
-spec(unhook(emqx_hooks:hookpoint(), emqx_hooks:action() | {module(), atom()}) -> ok). -spec unhook(emqx_hooks:hookpoint(), emqx_hooks:action() | {module(), atom()}) -> ok.
unhook(HookPoint, Action) -> unhook(HookPoint, Action) ->
emqx_hooks:del(HookPoint, Action). emqx_hooks:del(HookPoint, Action).
-spec(run_hook(emqx_hooks:hookpoint(), list(any())) -> ok | stop). -spec run_hook(emqx_hooks:hookpoint(), list(any())) -> ok | stop.
run_hook(HookPoint, Args) -> run_hook(HookPoint, Args) ->
emqx_hooks:run(HookPoint, Args). emqx_hooks:run(HookPoint, Args).
-spec(run_fold_hook(emqx_hooks:hookpoint(), list(any()), any()) -> any()). -spec run_fold_hook(emqx_hooks:hookpoint(), list(any()), any()) -> any().
run_fold_hook(HookPoint, Args, Acc) -> run_fold_hook(HookPoint, Args, Acc) ->
emqx_hooks:run_fold(HookPoint, Args, Acc). emqx_hooks:run_fold(HookPoint, Args, Acc).
@ -202,12 +212,18 @@ get_raw_config(KeyPath, Default) ->
update_config(KeyPath, UpdateReq) -> update_config(KeyPath, UpdateReq) ->
update_config(KeyPath, UpdateReq, #{}). update_config(KeyPath, UpdateReq, #{}).
-spec update_config(emqx_map_lib:config_key_path(), emqx_config:update_request(), -spec update_config(
emqx_config:update_opts()) -> emqx_map_lib:config_key_path(),
emqx_config:update_request(),
emqx_config:update_opts()
) ->
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
update_config([RootName | _] = KeyPath, UpdateReq, Opts) -> update_config([RootName | _] = KeyPath, UpdateReq, Opts) ->
emqx_config_handler:update_config(emqx_config:get_schema_mod(RootName), KeyPath, emqx_config_handler:update_config(
{{update, UpdateReq}, Opts}). emqx_config:get_schema_mod(RootName),
KeyPath,
{{update, UpdateReq}, Opts}
).
-spec remove_config(emqx_map_lib:config_key_path()) -> -spec remove_config(emqx_map_lib:config_key_path()) ->
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
@ -217,16 +233,22 @@ remove_config(KeyPath) ->
-spec remove_config(emqx_map_lib:config_key_path(), emqx_config:update_opts()) -> -spec remove_config(emqx_map_lib:config_key_path(), emqx_config:update_opts()) ->
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
remove_config([RootName | _] = KeyPath, Opts) -> remove_config([RootName | _] = KeyPath, Opts) ->
emqx_config_handler:update_config(emqx_config:get_schema_mod(RootName), emqx_config_handler:update_config(
KeyPath, {remove, Opts}). emqx_config:get_schema_mod(RootName),
KeyPath,
{remove, Opts}
).
-spec reset_config(emqx_map_lib:config_key_path(), emqx_config:update_opts()) -> -spec reset_config(emqx_map_lib:config_key_path(), emqx_config:update_opts()) ->
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
reset_config([RootName | _] = KeyPath, Opts) -> reset_config([RootName | _] = KeyPath, Opts) ->
case emqx_config:get_default_value(KeyPath) of case emqx_config:get_default_value(KeyPath) of
{ok, Default} -> {ok, Default} ->
emqx_config_handler:update_config(emqx_config:get_schema_mod(RootName), KeyPath, emqx_config_handler:update_config(
{{update, Default}, Opts}); emqx_config:get_schema_mod(RootName),
KeyPath,
{{update, Default}, Opts}
);
{error, _} = Error -> {error, _} = Error ->
Error Error
end. end.

View File

@ -18,16 +18,21 @@
-include("emqx.hrl"). -include("emqx.hrl").
-export([ authenticate/1 -export([
, authorize/3 authenticate/1,
]). authorize/3
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(authenticate(emqx_types:clientinfo()) -> -spec authenticate(emqx_types:clientinfo()) ->
{ok, map()} | {ok, map(), binary()} | {continue, map()} | {continue, binary(), map()} | {error, term()}). {ok, map()}
| {ok, map(), binary()}
| {continue, map()}
| {continue, binary(), map()}
| {error, term()}.
authenticate(Credential) -> authenticate(Credential) ->
case run_hooks('client.authenticate', [Credential], {ok, #{is_superuser => false}}) of case run_hooks('client.authenticate', [Credential], {ok, #{is_superuser => false}}) of
ok -> ok ->
@ -37,14 +42,16 @@ authenticate(Credential) ->
end. end.
%% @doc Check Authorization %% @doc Check Authorization
-spec authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic()) -spec authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic()) ->
-> allow | deny. allow | deny.
authorize(ClientInfo, PubSub, Topic) -> authorize(ClientInfo, PubSub, Topic) ->
Result = case emqx_authz_cache:is_enabled() of Result =
true -> check_authorization_cache(ClientInfo, PubSub, Topic); case emqx_authz_cache:is_enabled() of
false -> do_authorize(ClientInfo, PubSub, Topic) true -> check_authorization_cache(ClientInfo, PubSub, Topic);
end, false -> do_authorize(ClientInfo, PubSub, Topic)
inc_acl_metrics(Result), Result. end,
inc_acl_metrics(Result),
Result.
check_authorization_cache(ClientInfo, PubSub, Topic) -> check_authorization_cache(ClientInfo, PubSub, Topic) ->
case emqx_authz_cache:get_authz_cache(PubSub, Topic) of case emqx_authz_cache:get_authz_cache(PubSub, Topic) of
@ -60,7 +67,7 @@ check_authorization_cache(ClientInfo, PubSub, Topic) ->
do_authorize(ClientInfo, PubSub, Topic) -> do_authorize(ClientInfo, PubSub, Topic) ->
NoMatch = emqx:get_config([authorization, no_match], allow), NoMatch = emqx:get_config([authorization, no_match], allow),
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], NoMatch) of case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], NoMatch) of
allow -> allow; allow -> allow;
_Other -> deny _Other -> deny
end. end.

View File

@ -26,44 +26,45 @@
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-export([start_link/0 -export([start_link/0]).
]).
%% API %% API
-export([ activate/1 -export([
, activate/2 activate/1,
, activate/3 activate/2,
, deactivate/1 activate/3,
, deactivate/2 deactivate/1,
, deactivate/3 deactivate/2,
, delete_all_deactivated_alarms/0 deactivate/3,
, get_alarms/0 delete_all_deactivated_alarms/0,
, get_alarms/1 get_alarms/0,
, format/1 get_alarms/1,
]). format/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-record(activated_alarm, { -record(activated_alarm, {
name :: binary() | atom(), name :: binary() | atom(),
details :: map() | list(), details :: map() | list(),
message :: binary(), message :: binary(),
activate_at :: integer() activate_at :: integer()
}). }).
-record(deactivated_alarm, { -record(deactivated_alarm, {
activate_at :: integer(), activate_at :: integer(),
name :: binary() | atom(), name :: binary() | atom(),
details :: map() | list(), details :: map() | list(),
message :: binary(), message :: binary(),
deactivate_at :: integer() | infinity deactivate_at :: integer() | infinity
}). }).
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
@ -75,18 +76,26 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
mnesia(boot) -> mnesia(boot) ->
ok = mria:create_table(?ACTIVATED_ALARM, ok = mria:create_table(
[{type, set}, ?ACTIVATED_ALARM,
{storage, disc_copies}, [
{local_content, true}, {type, set},
{record_name, activated_alarm}, {storage, disc_copies},
{attributes, record_info(fields, activated_alarm)}]), {local_content, true},
ok = mria:create_table(?DEACTIVATED_ALARM, {record_name, activated_alarm},
[{type, ordered_set}, {attributes, record_info(fields, activated_alarm)}
{storage, disc_copies}, ]
{local_content, true}, ),
{record_name, deactivated_alarm}, ok = mria:create_table(
{attributes, record_info(fields, deactivated_alarm)}]). ?DEACTIVATED_ALARM,
[
{type, ordered_set},
{storage, disc_copies},
{local_content, true},
{record_name, deactivated_alarm},
{attributes, record_info(fields, deactivated_alarm)}
]
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
@ -124,10 +133,8 @@ get_alarms() ->
-spec get_alarms(all | activated | deactivated) -> [map()]. -spec get_alarms(all | activated | deactivated) -> [map()].
get_alarms(all) -> get_alarms(all) ->
gen_server:call(?MODULE, {get_alarms, all}); gen_server:call(?MODULE, {get_alarms, all});
get_alarms(activated) -> get_alarms(activated) ->
gen_server:call(?MODULE, {get_alarms, activated}); gen_server:call(?MODULE, {get_alarms, activated});
get_alarms(deactivated) -> get_alarms(deactivated) ->
gen_server:call(?MODULE, {get_alarms, deactivated}). gen_server:call(?MODULE, {get_alarms, deactivated}).
@ -139,17 +146,24 @@ format(#activated_alarm{name = Name, message = Message, activate_at = At, detail
node => node(), node => node(),
name => Name, name => Name,
message => Message, message => Message,
duration => (Now - At) div 1000, %% to millisecond %% to millisecond
duration => (Now - At) div 1000,
activate_at => to_rfc3339(At), activate_at => to_rfc3339(At),
details => Details details => Details
}; };
format(#deactivated_alarm{name = Name, message = Message, activate_at = At, details = Details, format(#deactivated_alarm{
deactivate_at = DAt}) -> name = Name,
message = Message,
activate_at = At,
details = Details,
deactivate_at = DAt
}) ->
#{ #{
node => node(), node => node(),
name => Name, name => Name,
message => Message, message => Message,
duration => (DAt - At) div 1000, %% to millisecond %% to millisecond
duration => (DAt - At) div 1000,
activate_at => to_rfc3339(At), activate_at => to_rfc3339(At),
deactivate_at => to_rfc3339(DAt), deactivate_at => to_rfc3339(DAt),
details => Details details => Details
@ -169,9 +183,11 @@ init([]) ->
{ok, #{}, get_validity_period()}. {ok, #{}, get_validity_period()}.
handle_call({activate_alarm, Name, Details, Message}, _From, State) -> handle_call({activate_alarm, Name, Details, Message}, _From, State) ->
Res = mria:transaction(mria:local_content_shard(), Res = mria:transaction(
mria:local_content_shard(),
fun create_activate_alarm/3, fun create_activate_alarm/3,
[Name, Details, Message]), [Name, Details, Message]
),
case Res of case Res of
{atomic, Alarm} -> {atomic, Alarm} ->
do_actions(activate, Alarm, emqx:get_config([alarm, actions])), do_actions(activate, Alarm, emqx:get_config([alarm, actions])),
@ -179,7 +195,6 @@ handle_call({activate_alarm, Name, Details, Message}, _From, State) ->
{aborted, Reason} -> {aborted, Reason} ->
{reply, Reason, State, get_validity_period()} {reply, Reason, State, get_validity_period()}
end; end;
handle_call({deactivate_alarm, Name, Details, Message}, _From, State) -> handle_call({deactivate_alarm, Name, Details, Message}, _From, State) ->
case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of
[] -> [] ->
@ -188,30 +203,29 @@ handle_call({deactivate_alarm, Name, Details, Message}, _From, State) ->
deactivate_alarm(Alarm, Details, Message), deactivate_alarm(Alarm, Details, Message),
{reply, ok, State, get_validity_period()} {reply, ok, State, get_validity_period()}
end; end;
handle_call(delete_all_deactivated_alarms, _From, State) -> handle_call(delete_all_deactivated_alarms, _From, State) ->
clear_table(?DEACTIVATED_ALARM), clear_table(?DEACTIVATED_ALARM),
{reply, ok, State, get_validity_period()}; {reply, ok, State, get_validity_period()};
handle_call({get_alarms, all}, _From, State) -> handle_call({get_alarms, all}, _From, State) ->
{atomic, Alarms} = {atomic, Alarms} =
mria:ro_transaction( mria:ro_transaction(
mria:local_content_shard(), mria:local_content_shard(),
fun() -> fun() ->
[normalize(Alarm) || [
Alarm <- ets:tab2list(?ACTIVATED_ALARM) normalize(Alarm)
++ ets:tab2list(?DEACTIVATED_ALARM)] || Alarm <-
end), ets:tab2list(?ACTIVATED_ALARM) ++
ets:tab2list(?DEACTIVATED_ALARM)
]
end
),
{reply, Alarms, State, get_validity_period()}; {reply, Alarms, State, get_validity_period()};
handle_call({get_alarms, activated}, _From, State) -> handle_call({get_alarms, activated}, _From, State) ->
Alarms = [normalize(Alarm) || Alarm <- ets:tab2list(?ACTIVATED_ALARM)], Alarms = [normalize(Alarm) || Alarm <- ets:tab2list(?ACTIVATED_ALARM)],
{reply, Alarms, State, get_validity_period()}; {reply, Alarms, State, get_validity_period()};
handle_call({get_alarms, deactivated}, _From, State) -> handle_call({get_alarms, deactivated}, _From, State) ->
Alarms = [normalize(Alarm) || Alarm <- ets:tab2list(?DEACTIVATED_ALARM)], Alarms = [normalize(Alarm) || Alarm <- ets:tab2list(?DEACTIVATED_ALARM)],
{reply, Alarms, State, get_validity_period()}; {reply, Alarms, State, get_validity_period()};
handle_call(Req, From, State) -> handle_call(Req, From, State) ->
?SLOG(error, #{msg => "unexpected_call", call_req => Req, from => From}), ?SLOG(error, #{msg => "unexpected_call", call_req => Req, from => From}),
{reply, ignored, State, get_validity_period()}. {reply, ignored, State, get_validity_period()}.
@ -224,7 +238,6 @@ handle_info(timeout, State) ->
Period = get_validity_period(), Period = get_validity_period(),
delete_expired_deactivated_alarms(erlang:system_time(microsecond) - Period * 1000), delete_expired_deactivated_alarms(erlang:system_time(microsecond) - Period * 1000),
{noreply, State, Period}; {noreply, State, Period};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info_req => Info}), ?SLOG(error, #{msg => "unexpected_info", info_req => Info}),
{noreply, State, get_validity_period()}. {noreply, State, get_validity_period()}.
@ -247,31 +260,50 @@ create_activate_alarm(Name, Details, Message) ->
[#activated_alarm{name = Name}] -> [#activated_alarm{name = Name}] ->
mnesia:abort({error, already_existed}); mnesia:abort({error, already_existed});
[] -> [] ->
Alarm = #activated_alarm{name = Name, Alarm = #activated_alarm{
name = Name,
details = Details, details = Details,
message = normalize_message(Name, iolist_to_binary(Message)), message = normalize_message(Name, iolist_to_binary(Message)),
activate_at = erlang:system_time(microsecond)}, activate_at = erlang:system_time(microsecond)
},
ok = mnesia:write(?ACTIVATED_ALARM, Alarm, write), ok = mnesia:write(?ACTIVATED_ALARM, Alarm, write),
Alarm Alarm
end. end.
deactivate_alarm(#activated_alarm{activate_at = ActivateAt, name = Name, deactivate_alarm(
details = Details0, message = Msg0}, Details, Message) -> #activated_alarm{
activate_at = ActivateAt,
name = Name,
details = Details0,
message = Msg0
},
Details,
Message
) ->
SizeLimit = emqx:get_config([alarm, size_limit]), SizeLimit = emqx:get_config([alarm, size_limit]),
case SizeLimit > 0 andalso (mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of case SizeLimit > 0 andalso (mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of
true -> true ->
case mnesia:dirty_first(?DEACTIVATED_ALARM) of case mnesia:dirty_first(?DEACTIVATED_ALARM) of
'$end_of_table' -> ok; '$end_of_table' -> ok;
ActivateAt2 -> ActivateAt2 -> mria:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
mria:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
end; end;
false -> ok false ->
ok
end, end,
HistoryAlarm = make_deactivated_alarm(ActivateAt, Name, Details0, Msg0, HistoryAlarm = make_deactivated_alarm(
erlang:system_time(microsecond)), ActivateAt,
DeActAlarm = make_deactivated_alarm(ActivateAt, Name, Details, Name,
normalize_message(Name, iolist_to_binary(Message)), Details0,
erlang:system_time(microsecond)), Msg0,
erlang:system_time(microsecond)
),
DeActAlarm = make_deactivated_alarm(
ActivateAt,
Name,
Details,
normalize_message(Name, iolist_to_binary(Message)),
erlang:system_time(microsecond)
),
mria:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm), mria:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
mria:dirty_delete(?ACTIVATED_ALARM, Name), mria:dirty_delete(?ACTIVATED_ALARM, Name),
do_actions(deactivate, DeActAlarm, emqx:get_config([alarm, actions])). do_actions(deactivate, DeActAlarm, emqx:get_config([alarm, actions])).
@ -282,22 +314,32 @@ make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
name = Name, name = Name,
details = Details, details = Details,
message = Message, message = Message,
deactivate_at = DeActivateAt}. deactivate_at = DeActivateAt
}.
deactivate_all_alarms() -> deactivate_all_alarms() ->
lists:foreach( lists:foreach(
fun(#activated_alarm{name = Name, fun(
details = Details, #activated_alarm{
message = Message, name = Name,
activate_at = ActivateAt}) -> details = Details,
mria:dirty_write(?DEACTIVATED_ALARM, message = Message,
activate_at = ActivateAt
}
) ->
mria:dirty_write(
?DEACTIVATED_ALARM,
#deactivated_alarm{ #deactivated_alarm{
activate_at = ActivateAt, activate_at = ActivateAt,
name = Name, name = Name,
details = Details, details = Details,
message = Message, message = Message,
deactivate_at = erlang:system_time(microsecond)}) deactivate_at = erlang:system_time(microsecond)
end, ets:tab2list(?ACTIVATED_ALARM)), }
)
end,
ets:tab2list(?ACTIVATED_ALARM)
),
clear_table(?ACTIVATED_ALARM). clear_table(?ACTIVATED_ALARM).
%% Delete all records from the given table, ignore result. %% Delete all records from the given table, ignore result.
@ -346,8 +388,14 @@ do_actions(deactivate, Alarm = #deactivated_alarm{name = Name}, [log | More]) ->
do_actions(Operation, Alarm, [publish | More]) -> do_actions(Operation, Alarm, [publish | More]) ->
Topic = topic(Operation), Topic = topic(Operation),
{ok, Payload} = emqx_json:safe_encode(normalize(Alarm)), {ok, Payload} = emqx_json:safe_encode(normalize(Alarm)),
Message = emqx_message:make(?MODULE, 0, Topic, Payload, #{sys => true}, Message = emqx_message:make(
#{properties => #{'Content-Type' => <<"application/json">>}}), ?MODULE,
0,
Topic,
Payload,
#{sys => true},
#{properties => #{'Content-Type' => <<"application/json">>}}
),
_ = emqx_broker:safe_publish(Message), _ = emqx_broker:safe_publish(Message),
do_actions(Operation, Alarm, More). do_actions(Operation, Alarm, More).
@ -356,28 +404,37 @@ topic(activate) ->
topic(deactivate) -> topic(deactivate) ->
emqx_topic:systop(<<"alarms/deactivate">>). emqx_topic:systop(<<"alarms/deactivate">>).
normalize(#activated_alarm{name = Name, normalize(#activated_alarm{
details = Details, name = Name,
message = Message, details = Details,
activate_at = ActivateAt}) -> message = Message,
#{name => Name, activate_at = ActivateAt
details => Details, }) ->
message => Message, #{
activate_at => ActivateAt, name => Name,
deactivate_at => infinity, details => Details,
activated => true}; message => Message,
normalize(#deactivated_alarm{activate_at = ActivateAt, activate_at => ActivateAt,
name = Name, deactivate_at => infinity,
details = Details, activated => true
message = Message, };
deactivate_at = DeactivateAt}) -> normalize(#deactivated_alarm{
#{name => Name, activate_at = ActivateAt,
details => Details, name = Name,
message => Message, details = Details,
activate_at => ActivateAt, message = Message,
deactivate_at => DeactivateAt, deactivate_at = DeactivateAt
activated => false}. }) ->
#{
name => Name,
details => Details,
message => Message,
activate_at => ActivateAt,
deactivate_at => DeactivateAt,
activated => false
}.
normalize_message(Name, <<"">>) -> normalize_message(Name, <<"">>) ->
list_to_binary(io_lib:format("~p", [Name])); list_to_binary(io_lib:format("~p", [Name]));
normalize_message(_Name, Message) -> Message. normalize_message(_Name, Message) ->
Message.

View File

@ -22,18 +22,19 @@
-include("logger.hrl"). -include("logger.hrl").
-include_lib("lc/include/lc.hrl"). -include_lib("lc/include/lc.hrl").
%% gen_event callbacks %% gen_event callbacks
-export([ init/1 -export([
, handle_event/2 init/1,
, handle_call/2 handle_event/2,
, handle_info/2 handle_call/2,
, terminate/2 handle_info/2,
]). terminate/2
]).
-export([ load/0 -export([
, unload/0 load/0,
]). unload/0
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
@ -52,43 +53,44 @@ unload() ->
init({_Args, {alarm_handler, _ExistingAlarms}}) -> init({_Args, {alarm_handler, _ExistingAlarms}}) ->
{ok, []}; {ok, []};
init(_) -> init(_) ->
{ok, []}. {ok, []}.
handle_event({set_alarm, {system_memory_high_watermark, []}}, State) -> handle_event({set_alarm, {system_memory_high_watermark, []}}, State) ->
HighWatermark = emqx_os_mon:get_sysmem_high_watermark(), HighWatermark = emqx_os_mon:get_sysmem_high_watermark(),
Message = to_bin("System memory usage is higher than ~p%", [HighWatermark]), Message = to_bin("System memory usage is higher than ~p%", [HighWatermark]),
emqx_alarm:activate(high_system_memory_usage, emqx_alarm:activate(
#{high_watermark => HighWatermark}, Message), high_system_memory_usage,
#{high_watermark => HighWatermark},
Message
),
{ok, State}; {ok, State};
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) -> handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
HighWatermark = emqx_os_mon:get_procmem_high_watermark(), HighWatermark = emqx_os_mon:get_procmem_high_watermark(),
Message = to_bin("Process memory usage is higher than ~p%", [HighWatermark]), Message = to_bin("Process memory usage is higher than ~p%", [HighWatermark]),
emqx_alarm:activate(high_process_memory_usage, emqx_alarm:activate(
#{pid => list_to_binary(pid_to_list(Pid)), high_process_memory_usage,
high_watermark => HighWatermark}, Message), #{
pid => list_to_binary(pid_to_list(Pid)),
high_watermark => HighWatermark
},
Message
),
{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};
handle_event({set_alarm, {?LC_ALARM_ID_RUNQ, Info}}, State) -> handle_event({set_alarm, {?LC_ALARM_ID_RUNQ, Info}}, State) ->
#{node := Node, runq_length := Len} = Info, #{node := Node, runq_length := Len} = Info,
Message = to_bin("VM is overloaded on node: ~p: ~p", [Node, Len]), Message = to_bin("VM is overloaded on node: ~p: ~p", [Node, Len]),
emqx_alarm:activate(runq_overload, Info, Message), emqx_alarm:activate(runq_overload, Info, Message),
{ok, State}; {ok, State};
handle_event({clear_alarm, ?LC_ALARM_ID_RUNQ}, State) -> handle_event({clear_alarm, ?LC_ALARM_ID_RUNQ}, State) ->
_ = emqx_alarm:deactivate(runq_overload), _ = emqx_alarm:deactivate(runq_overload),
{ok, State}; {ok, State};
handle_event(_, State) -> handle_event(_, State) ->
{ok, State}. {ok, State}.

View File

@ -18,16 +18,17 @@
-behaviour(application). -behaviour(application).
-export([ start/2 -export([
, prep_stop/1 start/2,
, stop/1 prep_stop/1,
, get_description/0 stop/1,
, get_release/0 get_description/0,
, set_init_config_load_done/0 get_release/0,
, get_init_config_load_done/0 set_init_config_load_done/0,
, set_init_tnx_id/1 get_init_config_load_done/0,
, get_init_tnx_id/0 set_init_tnx_id/1,
]). get_init_tnx_id/0
]).
-include("emqx.hrl"). -include("emqx.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -54,8 +55,8 @@ start(_Type, _Args) ->
prep_stop(_State) -> prep_stop(_State) ->
ok = emqx_alarm_handler:unload(), ok = emqx_alarm_handler:unload(),
emqx_config:remove_handlers(), emqx_config:remove_handlers(),
emqx_boot:is_enabled(listeners) emqx_boot:is_enabled(listeners) andalso
andalso emqx_listeners:stop(). emqx_listeners:stop().
stop(_State) -> ok. stop(_State) -> ok.
@ -93,14 +94,19 @@ maybe_start_listeners() ->
maybe_start_quicer() -> maybe_start_quicer() ->
case is_quicer_app_present() andalso is_quic_listener_configured() of case is_quicer_app_present() andalso is_quic_listener_configured() of
true -> {ok, _} = application:ensure_all_started(quicer), ok; true ->
false -> ok {ok, _} = application:ensure_all_started(quicer),
ok;
false ->
ok
end. end.
is_quicer_app_present() -> is_quicer_app_present() ->
case application:load(quicer) of case application:load(quicer) of
ok -> true; ok ->
{error, {already_loaded, _}} -> true; true;
{error, {already_loaded, _}} ->
true;
_ -> _ ->
?SLOG(info, #{msg => "quicer_app_not_found"}), ?SLOG(info, #{msg => "quicer_app_not_found"}),
false false

View File

@ -31,65 +31,69 @@
-define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). -define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
%% The authentication entrypoint. %% The authentication entrypoint.
-export([ authenticate/2 -export([authenticate/2]).
]).
%% Authenticator manager process start/stop %% Authenticator manager process start/stop
-export([ start_link/0 -export([
, stop/0 start_link/0,
, get_providers/0 stop/0,
]). get_providers/0
]).
%% Authenticator management APIs %% Authenticator management APIs
-export([ initialize_authentication/2 -export([
, register_provider/2 initialize_authentication/2,
, register_providers/1 register_provider/2,
, deregister_provider/1 register_providers/1,
, deregister_providers/1 deregister_provider/1,
, create_chain/1 deregister_providers/1,
, delete_chain/1 create_chain/1,
, lookup_chain/1 delete_chain/1,
, list_chains/0 lookup_chain/1,
, list_chain_names/0 list_chains/0,
, create_authenticator/2 list_chain_names/0,
, delete_authenticator/2 create_authenticator/2,
, update_authenticator/3 delete_authenticator/2,
, lookup_authenticator/2 update_authenticator/3,
, list_authenticators/1 lookup_authenticator/2,
, move_authenticator/3 list_authenticators/1,
]). move_authenticator/3
]).
%% APIs for observer built_in_database %% APIs for observer built_in_database
-export([ import_users/3 -export([
, add_user/3 import_users/3,
, delete_user/3 add_user/3,
, update_user/4 delete_user/3,
, lookup_user/3 update_user/4,
, list_users/3 lookup_user/3,
]). list_users/3
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
%% utility functions %% utility functions
-export([ authenticator_id/1 -export([authenticator_id/1]).
]).
%% proxy callback %% proxy callback
-export([ pre_config_update/3 -export([
, post_config_update/5 pre_config_update/3,
]). post_config_update/5
]).
-export_type([ authenticator_id/0 -export_type([
, position/0 authenticator_id/0,
, chain_name/0 position/0,
]). chain_name/0
]).
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
@ -104,85 +108,108 @@
-type authn_type() :: atom() | {atom(), atom()}. -type authn_type() :: atom() | {atom(), atom()}.
-type provider() :: module(). -type provider() :: module().
-type chain() :: #{name := chain_name(), -type chain() :: #{
authenticators := [authenticator()]}. name := chain_name(),
authenticators := [authenticator()]
}.
-type authenticator() :: #{id := authenticator_id(), -type authenticator() :: #{
provider := provider(), id := authenticator_id(),
enable := boolean(), provider := provider(),
state := map()}. enable := boolean(),
state := map()
}.
-type config() :: emqx_authentication_config:config(). -type config() :: emqx_authentication_config:config().
-type state() :: #{atom() => term()}. -type state() :: #{atom() => term()}.
-type extra() :: #{is_superuser := boolean(), -type extra() :: #{
atom() => term()}. is_superuser := boolean(),
-type user_info() :: #{user_id := binary(), atom() => term()
atom() => term()}. }.
-type user_info() :: #{
user_id := binary(),
atom() => term()
}.
%% @doc check_config takes raw config from config file, %% @doc check_config takes raw config from config file,
%% parse and validate it, and return parsed result. %% parse and validate it, and return parsed result.
-callback check_config(config()) -> config(). -callback check_config(config()) -> config().
-callback create(AuthenticatorID, Config) -callback create(AuthenticatorID, Config) ->
-> {ok, State} {ok, State}
| {error, term()} | {error, term()}
when AuthenticatorID::authenticator_id(), Config::config(), State::state(). when
AuthenticatorID :: authenticator_id(), Config :: config(), State :: state().
-callback update(Config, State) -callback update(Config, State) ->
-> {ok, NewState} {ok, NewState}
| {error, term()} | {error, term()}
when Config::config(), State::state(), NewState::state(). when
Config :: config(), State :: state(), NewState :: state().
-callback authenticate(Credential, State) -callback authenticate(Credential, State) ->
-> ignore ignore
| {ok, Extra} | {ok, Extra}
| {ok, Extra, AuthData} | {ok, Extra, AuthData}
| {continue, AuthCache} | {continue, AuthCache}
| {continue, AuthData, AuthCache} | {continue, AuthData, AuthCache}
| {error, term()} | {error, term()}
when Credential::map(), State::state(), Extra::extra(), AuthData::binary(), AuthCache::map(). when
Credential :: map(),
State :: state(),
Extra :: extra(),
AuthData :: binary(),
AuthCache :: map().
-callback destroy(State) -callback destroy(State) ->
-> ok ok
when State::state(). when
State :: state().
-callback import_users(Filename, State) -callback import_users(Filename, State) ->
-> ok ok
| {error, term()} | {error, term()}
when Filename::binary(), State::state(). when
Filename :: binary(), State :: state().
-callback add_user(UserInfo, State) -callback add_user(UserInfo, State) ->
-> {ok, User} {ok, User}
| {error, term()} | {error, term()}
when UserInfo::user_info(), State::state(), User::user_info(). when
UserInfo :: user_info(), State :: state(), User :: user_info().
-callback delete_user(UserID, State) -callback delete_user(UserID, State) ->
-> ok ok
| {error, term()} | {error, term()}
when UserID::binary(), State::state(). when
UserID :: binary(), State :: state().
-callback update_user(UserID, UserInfo, State) -callback update_user(UserID, UserInfo, State) ->
-> {ok, User} {ok, User}
| {error, term()} | {error, term()}
when UserID::binary(), UserInfo::map(), State::state(), User::user_info(). when
UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info().
-callback lookup_user(UserID, UserInfo, State) -callback lookup_user(UserID, UserInfo, State) ->
-> {ok, User} {ok, User}
| {error, term()} | {error, term()}
when UserID::binary(), UserInfo::map(), State::state(), User::user_info(). when
UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info().
-callback list_users(State) -callback list_users(State) ->
-> {ok, Users} {ok, Users}
when State::state(), Users::[user_info()]. when
State :: state(), Users :: [user_info()].
-optional_callbacks([ import_users/2 -optional_callbacks([
, add_user/2 import_users/2,
, delete_user/2 add_user/2,
, update_user/3 delete_user/2,
, lookup_user/3 update_user/3,
, list_users/1 lookup_user/3,
, check_config/1 list_users/1,
]). check_config/1
]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Authenticate %% Authenticate
@ -235,22 +262,26 @@ authenticator_id(Config) ->
%% @doc Call this API to initialize authenticators implemented in another APP. %% @doc Call this API to initialize authenticators implemented in another APP.
-spec initialize_authentication(chain_name(), [config()]) -> ok. -spec initialize_authentication(chain_name(), [config()]) -> ok.
initialize_authentication(_, []) -> ok; initialize_authentication(_, []) ->
ok;
initialize_authentication(ChainName, AuthenticatorsConfig) -> initialize_authentication(ChainName, AuthenticatorsConfig) ->
_ = create_chain(ChainName), _ = create_chain(ChainName),
CheckedConfig = to_list(AuthenticatorsConfig), CheckedConfig = to_list(AuthenticatorsConfig),
lists:foreach(fun(AuthenticatorConfig) -> lists:foreach(
case create_authenticator(ChainName, AuthenticatorConfig) of fun(AuthenticatorConfig) ->
{ok, _} -> case create_authenticator(ChainName, AuthenticatorConfig) of
ok; {ok, _} ->
{error, Reason} -> ok;
?SLOG(error, #{ {error, Reason} ->
msg => "failed_to_create_authenticator", ?SLOG(error, #{
authenticator => authenticator_id(AuthenticatorConfig), msg => "failed_to_create_authenticator",
reason => Reason authenticator => authenticator_id(AuthenticatorConfig),
}) reason => Reason
end })
end, CheckedConfig). end
end,
CheckedConfig
).
-spec start_link() -> {ok, pid()} | ignore | {error, term()}. -spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() -> start_link() ->
@ -392,32 +423,38 @@ init(_Opts) ->
handle_call(get_providers, _From, #{providers := Providers} = State) -> handle_call(get_providers, _From, #{providers := Providers} = State) ->
reply(Providers, State); reply(Providers, State);
handle_call({register_providers, Providers}, _From, handle_call(
#{providers := Reg0} = State) -> {register_providers, Providers},
_From,
#{providers := Reg0} = State
) ->
case lists:filter(fun({T, _}) -> maps:is_key(T, Reg0) end, Providers) of case lists:filter(fun({T, _}) -> maps:is_key(T, Reg0) end, Providers) of
[] -> [] ->
Reg = lists:foldl(fun({AuthNType, Module}, Pin) -> Reg = lists:foldl(
Pin#{AuthNType => Module} fun({AuthNType, Module}, Pin) ->
end, Reg0, Providers), Pin#{AuthNType => Module}
end,
Reg0,
Providers
),
reply(ok, State#{providers := Reg}); reply(ok, State#{providers := Reg});
Clashes -> Clashes ->
reply({error, {authentication_type_clash, Clashes}}, State) reply({error, {authentication_type_clash, Clashes}}, State)
end; end;
handle_call({deregister_providers, AuthNTypes}, _From, #{providers := Providers} = State) -> handle_call({deregister_providers, AuthNTypes}, _From, #{providers := Providers} = State) ->
reply(ok, State#{providers := maps:without(AuthNTypes, Providers)}); reply(ok, State#{providers := maps:without(AuthNTypes, Providers)});
handle_call({create_chain, Name}, _From, State) -> handle_call({create_chain, Name}, _From, State) ->
case ets:member(?CHAINS_TAB, Name) of case ets:member(?CHAINS_TAB, Name) of
true -> true ->
reply({error, {already_exists, {chain, Name}}}, State); reply({error, {already_exists, {chain, Name}}}, State);
false -> false ->
Chain = #chain{name = Name, Chain = #chain{
authenticators = []}, name = Name,
authenticators = []
},
true = ets:insert(?CHAINS_TAB, Chain), true = ets:insert(?CHAINS_TAB, Chain),
reply({ok, serialize_chain(Chain)}, State) reply({ok, serialize_chain(Chain)}, State)
end; end;
handle_call({delete_chain, Name}, _From, State) -> handle_call({delete_chain, Name}, _From, State) ->
case ets:lookup(?CHAINS_TAB, Name) of case ets:lookup(?CHAINS_TAB, Name) of
[] -> [] ->
@ -427,59 +464,48 @@ handle_call({delete_chain, Name}, _From, State) ->
true = ets:delete(?CHAINS_TAB, Name), true = ets:delete(?CHAINS_TAB, Name),
reply(ok, maybe_unhook(State)) reply(ok, maybe_unhook(State))
end; end;
handle_call({create_authenticator, ChainName, Config}, _From, #{providers := Providers} = State) -> handle_call({create_authenticator, ChainName, Config}, _From, #{providers := Providers} = State) ->
UpdateFun = fun(Chain) -> UpdateFun = fun(Chain) ->
handle_create_authenticator(Chain, Config, Providers) handle_create_authenticator(Chain, Config, Providers)
end, end,
Reply = update_chain(ChainName, UpdateFun), Reply = update_chain(ChainName, UpdateFun),
reply(Reply, maybe_hook(State)); reply(Reply, maybe_hook(State));
handle_call({delete_authenticator, ChainName, AuthenticatorID}, _From, State) -> handle_call({delete_authenticator, ChainName, AuthenticatorID}, _From, State) ->
UpdateFun = fun(Chain) -> UpdateFun = fun(Chain) ->
handle_delete_authenticator(Chain, AuthenticatorID) handle_delete_authenticator(Chain, AuthenticatorID)
end, end,
Reply = update_chain(ChainName, UpdateFun), Reply = update_chain(ChainName, UpdateFun),
reply(Reply, maybe_unhook(State)); reply(Reply, maybe_unhook(State));
handle_call({update_authenticator, ChainName, AuthenticatorID, Config}, _From, State) -> handle_call({update_authenticator, ChainName, AuthenticatorID, Config}, _From, State) ->
UpdateFun = fun(Chain) -> UpdateFun = fun(Chain) ->
handle_update_authenticator(Chain, AuthenticatorID, Config) handle_update_authenticator(Chain, AuthenticatorID, Config)
end, end,
Reply = update_chain(ChainName, UpdateFun), Reply = update_chain(ChainName, UpdateFun),
reply(Reply, State); reply(Reply, State);
handle_call({move_authenticator, ChainName, AuthenticatorID, Position}, _From, State) -> handle_call({move_authenticator, ChainName, AuthenticatorID, Position}, _From, State) ->
UpdateFun = fun(Chain) -> UpdateFun = fun(Chain) ->
handle_move_authenticator(Chain, AuthenticatorID, Position) handle_move_authenticator(Chain, AuthenticatorID, Position)
end, end,
Reply = update_chain(ChainName, UpdateFun), Reply = update_chain(ChainName, UpdateFun),
reply(Reply, State); reply(Reply, State);
handle_call({import_users, ChainName, AuthenticatorID, Filename}, _From, State) -> handle_call({import_users, ChainName, AuthenticatorID, Filename}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, import_users, [Filename]), Reply = call_authenticator(ChainName, AuthenticatorID, import_users, [Filename]),
reply(Reply, State); reply(Reply, State);
handle_call({add_user, ChainName, AuthenticatorID, UserInfo}, _From, State) -> handle_call({add_user, ChainName, AuthenticatorID, UserInfo}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, add_user, [UserInfo]), Reply = call_authenticator(ChainName, AuthenticatorID, add_user, [UserInfo]),
reply(Reply, State); reply(Reply, State);
handle_call({delete_user, ChainName, AuthenticatorID, UserID}, _From, State) -> handle_call({delete_user, ChainName, AuthenticatorID, UserID}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, delete_user, [UserID]), Reply = call_authenticator(ChainName, AuthenticatorID, delete_user, [UserID]),
reply(Reply, State); reply(Reply, State);
handle_call({update_user, ChainName, AuthenticatorID, UserID, NewUserInfo}, _From, State) -> handle_call({update_user, ChainName, AuthenticatorID, UserID, NewUserInfo}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, update_user, [UserID, NewUserInfo]), Reply = call_authenticator(ChainName, AuthenticatorID, update_user, [UserID, NewUserInfo]),
reply(Reply, State); reply(Reply, State);
handle_call({lookup_user, ChainName, AuthenticatorID, UserID}, _From, State) -> handle_call({lookup_user, ChainName, AuthenticatorID, UserID}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, lookup_user, [UserID]), Reply = call_authenticator(ChainName, AuthenticatorID, lookup_user, [UserID]),
reply(Reply, State); reply(Reply, State);
handle_call({list_users, ChainName, AuthenticatorID, FuzzyParams}, _From, State) -> handle_call({list_users, ChainName, AuthenticatorID, FuzzyParams}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, list_users, [FuzzyParams]), Reply = call_authenticator(ChainName, AuthenticatorID, list_users, [FuzzyParams]),
reply(Reply, State); reply(Reply, State);
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -494,10 +520,15 @@ handle_info(Info, State) ->
terminate(Reason, _State) -> terminate(Reason, _State) ->
case Reason of case Reason of
normal -> ok; normal ->
{shutdown, _} -> ok; ok;
Other -> ?SLOG(error, #{msg => "emqx_authentication_terminating", {shutdown, _} ->
reason => Other}) ok;
Other ->
?SLOG(error, #{
msg => "emqx_authentication_terminating",
reason => Other
})
end, end,
emqx_config_handler:remove_handler([?CONF_ROOT]), emqx_config_handler:remove_handler([?CONF_ROOT]),
emqx_config_handler:remove_handler([listeners, '?', '?', ?CONF_ROOT]), emqx_config_handler:remove_handler([listeners, '?', '?', ?CONF_ROOT]),
@ -521,15 +552,18 @@ handle_update_authenticator(Chain, AuthenticatorID, Config) ->
case Provider:update(Config, ST) of case Provider:update(Config, ST) of
{ok, NewST} -> {ok, NewST} ->
NewAuthenticator = Authenticator#authenticator{ NewAuthenticator = Authenticator#authenticator{
state = NewST, state = NewST,
enable = maps:get(enable, Config)}, enable = maps:get(enable, Config)
},
NewAuthenticators = replace_authenticator( NewAuthenticators = replace_authenticator(
AuthenticatorID, AuthenticatorID,
NewAuthenticator, NewAuthenticator,
Authenticators), Authenticators
),
true = ets:insert( true = ets:insert(
?CHAINS_TAB, ?CHAINS_TAB,
Chain#chain{authenticators = NewAuthenticators}), Chain#chain{authenticators = NewAuthenticators}
),
{ok, serialize_authenticator(NewAuthenticator)}; {ok, serialize_authenticator(NewAuthenticator)};
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
@ -541,8 +575,8 @@ handle_update_authenticator(Chain, AuthenticatorID, Config) ->
handle_delete_authenticator(Chain, AuthenticatorID) -> handle_delete_authenticator(Chain, AuthenticatorID) ->
MatchFun = fun(#authenticator{id = ID}) -> MatchFun = fun(#authenticator{id = ID}) ->
ID =:= AuthenticatorID ID =:= AuthenticatorID
end, end,
case do_delete_authenticators(MatchFun, Chain) of case do_delete_authenticators(MatchFun, Chain) of
[] -> {error, {not_found, {authenticator, AuthenticatorID}}}; [] -> {error, {not_found, {authenticator, AuthenticatorID}}};
[AuthenticatorID] -> ok [AuthenticatorID] -> ok
@ -569,9 +603,11 @@ handle_create_authenticator(Chain, Config, Providers) ->
{ok, Authenticator} -> {ok, Authenticator} ->
NAuthenticators = NAuthenticators =
Authenticators ++ Authenticators ++
[Authenticator#authenticator{enable = maps:get(enable, Config)}], [Authenticator#authenticator{enable = maps:get(enable, Config)}],
true = ets:insert(?CHAINS_TAB, true = ets:insert(
Chain#chain{authenticators = NAuthenticators}), ?CHAINS_TAB,
Chain#chain{authenticators = NAuthenticators}
),
{ok, serialize_authenticator(Authenticator)}; {ok, serialize_authenticator(Authenticator)};
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
@ -593,23 +629,28 @@ do_authenticate([#authenticator{id = ID, provider = Provider, state = State} | M
{stop, Result} {stop, Result}
catch catch
Class:Reason:Stacktrace -> Class:Reason:Stacktrace ->
?SLOG(warning, #{msg => "unexpected_error_in_authentication", ?SLOG(warning, #{
exception => Class, msg => "unexpected_error_in_authentication",
reason => Reason, exception => Class,
stacktrace => Stacktrace, reason => Reason,
authenticator => ID}), stacktrace => Stacktrace,
authenticator => ID
}),
do_authenticate(More, Credential) do_authenticate(More, Credential)
end. end.
reply(Reply, State) -> reply(Reply, State) ->
{reply, Reply, State}. {reply, Reply, State}.
create_chain_table() -> create_chain_table() ->
try try
_ = ets:new(?CHAINS_TAB, [named_table, set, public, _ = ets:new(?CHAINS_TAB, [
{keypos, #chain.name}, named_table,
{read_concurrency, true}]), set,
public,
{keypos, #chain.name},
{read_concurrency, true}
]),
ok ok
catch catch
error:badarg -> ok error:badarg -> ok
@ -629,9 +670,15 @@ global_chain(_) ->
'unknown:global'. 'unknown:global'.
maybe_hook(#{hooked := false} = State) -> maybe_hook(#{hooked := false} = State) ->
case lists:any(fun(#chain{authenticators = []}) -> false; case
(_) -> true lists:any(
end, ets:tab2list(?CHAINS_TAB)) of fun
(#chain{authenticators = []}) -> false;
(_) -> true
end,
ets:tab2list(?CHAINS_TAB)
)
of
true -> true ->
_ = emqx:hook('client.authenticate', {?MODULE, authenticate, []}), _ = emqx:hook('client.authenticate', {?MODULE, authenticate, []}),
State#{hooked => true}; State#{hooked => true};
@ -642,9 +689,15 @@ maybe_hook(State) ->
State. State.
maybe_unhook(#{hooked := true} = State) -> maybe_unhook(#{hooked := true} = State) ->
case lists:all(fun(#chain{authenticators = []}) -> true; case
(_) -> false lists:all(
end, ets:tab2list(?CHAINS_TAB)) of fun
(#chain{authenticators = []}) -> true;
(_) -> false
end,
ets:tab2list(?CHAINS_TAB)
)
of
true -> true ->
_ = emqx:unhook('client.authenticate', {?MODULE, authenticate, []}), _ = emqx:unhook('client.authenticate', {?MODULE, authenticate, []}),
State#{hooked => false}; State#{hooked => false};
@ -661,10 +714,12 @@ do_create_authenticator(AuthenticatorID, #{enable := Enable} = Config, Providers
Provider -> Provider ->
case Provider:create(AuthenticatorID, Config) of case Provider:create(AuthenticatorID, Config) of
{ok, State} -> {ok, State} ->
Authenticator = #authenticator{id = AuthenticatorID, Authenticator = #authenticator{
provider = Provider, id = AuthenticatorID,
enable = Enable, provider = Provider,
state = State}, enable = Enable,
state = State
},
{ok, Authenticator}; {ok, Authenticator};
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
@ -675,8 +730,9 @@ do_delete_authenticators(MatchFun, #chain{authenticators = Authenticators} = Cha
{Matching, Others} = lists:partition(MatchFun, Authenticators), {Matching, Others} = lists:partition(MatchFun, Authenticators),
MatchingIDs = lists:map( MatchingIDs = lists:map(
fun(#authenticator{id = ID}) -> ID end, fun(#authenticator{id = ID}) -> ID end,
Matching), Matching
),
ok = lists:foreach(fun do_destroy_authenticator/1, Matching), ok = lists:foreach(fun do_destroy_authenticator/1, Matching),
true = ets:insert(?CHAINS_TAB, Chain#chain{authenticators = Others}), true = ets:insert(?CHAINS_TAB, Chain#chain{authenticators = Others}),
@ -708,8 +764,12 @@ do_move_authenticator(ID, Authenticators, Position) ->
insert(_, [], {_, RelatedID}, _) -> insert(_, [], {_, RelatedID}, _) ->
{error, {not_found, {authenticator, RelatedID}}}; {error, {not_found, {authenticator, RelatedID}}};
insert(Authenticator, [#authenticator{id = RelatedID} = Related | Rest], insert(
{Relative, RelatedID}, Acc) -> Authenticator,
[#authenticator{id = RelatedID} = Related | Rest],
{Relative, RelatedID},
Acc
) ->
case Relative of case Relative of
before -> before ->
{ok, lists:reverse(Acc) ++ [Authenticator, Related | Rest]}; {ok, lists:reverse(Acc) ++ [Authenticator, Related | Rest]};
@ -744,24 +804,30 @@ call_authenticator(ChainName, AuthenticatorID, Func, Args) ->
end, end,
update_chain(ChainName, UpdateFun). update_chain(ChainName, UpdateFun).
serialize_chain(#chain{name = Name, serialize_chain(#chain{
authenticators = Authenticators}) -> name = Name,
#{ name => Name authenticators = Authenticators
, authenticators => serialize_authenticators(Authenticators) }) ->
}. #{
name => Name,
authenticators => serialize_authenticators(Authenticators)
}.
serialize_authenticators(Authenticators) -> serialize_authenticators(Authenticators) ->
[serialize_authenticator(Authenticator) || Authenticator <- Authenticators]. [serialize_authenticator(Authenticator) || Authenticator <- Authenticators].
serialize_authenticator(#authenticator{id = ID, serialize_authenticator(#authenticator{
provider = Provider, id = ID,
enable = Enable, provider = Provider,
state = State}) -> enable = Enable,
#{ id => ID state = State
, provider => Provider }) ->
, enable => Enable #{
, state => State id => ID,
}. provider => Provider,
enable => Enable,
state => State
}.
authn_type(#{mechanism := Mechanism, backend := Backend}) -> authn_type(#{mechanism := Mechanism, backend := Backend}) ->
{Mechanism, Backend}; {Mechanism, Backend};

View File

@ -19,13 +19,15 @@
-behaviour(emqx_config_handler). -behaviour(emqx_config_handler).
-export([ pre_config_update/3 -export([
, post_config_update/5 pre_config_update/3,
]). post_config_update/5
]).
-export([ authenticator_id/1 -export([
, authn_type/1 authenticator_id/1,
]). authn_type/1
]).
-ifdef(TEST). -ifdef(TEST).
-export([convert_certs/2, convert_certs/3, clear_certs/2]). -export([convert_certs/2, convert_certs/3, clear_certs/2]).
@ -36,32 +38,35 @@
-include("logger.hrl"). -include("logger.hrl").
-include("emqx_authentication.hrl"). -include("emqx_authentication.hrl").
-type parsed_config() :: #{mechanism := atom(), -type parsed_config() :: #{
backend => atom(), mechanism := atom(),
atom() => term()}. backend => atom(),
atom() => term()
}.
-type raw_config() :: #{binary() => term()}. -type raw_config() :: #{binary() => term()}.
-type config() :: parsed_config() | raw_config(). -type config() :: parsed_config() | raw_config().
-type authenticator_id() :: emqx_authentication:authenticator_id(). -type authenticator_id() :: emqx_authentication:authenticator_id().
-type position() :: emqx_authentication:position(). -type position() :: emqx_authentication:position().
-type chain_name() :: emqx_authentication:chain_name(). -type chain_name() :: emqx_authentication:chain_name().
-type update_request() :: {create_authenticator, chain_name(), map()} -type update_request() ::
| {delete_authenticator, chain_name(), authenticator_id()} {create_authenticator, chain_name(), map()}
| {update_authenticator, chain_name(), authenticator_id(), map()} | {delete_authenticator, chain_name(), authenticator_id()}
| {move_authenticator, chain_name(), authenticator_id(), position()}. | {update_authenticator, chain_name(), authenticator_id(), map()}
| {move_authenticator, chain_name(), authenticator_id(), position()}.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Callbacks of config handler %% Callbacks of config handler
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -spec pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) ->
-> {ok, map() | list()} | {error, term()}. {ok, map() | list()} | {error, term()}.
pre_config_update(_, UpdateReq, OldConfig) -> pre_config_update(_, UpdateReq, OldConfig) ->
try do_pre_config_update(UpdateReq, to_list(OldConfig)) of try do_pre_config_update(UpdateReq, to_list(OldConfig)) of
{error, Reason} -> {error, Reason}; {error, Reason} -> {error, Reason};
{ok, NewConfig} -> {ok, return_map(NewConfig)} {ok, NewConfig} -> {ok, return_map(NewConfig)}
catch catch
throw : Reason -> throw:Reason ->
{error, Reason} {error, Reason}
end. end.
@ -70,23 +75,29 @@ do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) ->
NConfig = convert_certs(CertsDir, Config), NConfig = convert_certs(CertsDir, Config),
{ok, OldConfig ++ [NConfig]}; {ok, OldConfig ++ [NConfig]};
do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) -> do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) ->
NewConfig = lists:filter(fun(OldConfig0) -> NewConfig = lists:filter(
AuthenticatorID =/= authenticator_id(OldConfig0) fun(OldConfig0) ->
end, OldConfig), AuthenticatorID =/= authenticator_id(OldConfig0)
end,
OldConfig
),
{ok, NewConfig}; {ok, NewConfig};
do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) -> do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) ->
CertsDir = certs_dir(ChainName, AuthenticatorID), CertsDir = certs_dir(ChainName, AuthenticatorID),
NewConfig = lists:map( NewConfig = lists:map(
fun(OldConfig0) -> fun(OldConfig0) ->
case AuthenticatorID =:= authenticator_id(OldConfig0) of case AuthenticatorID =:= authenticator_id(OldConfig0) of
true -> convert_certs(CertsDir, Config, OldConfig0); true -> convert_certs(CertsDir, Config, OldConfig0);
false -> OldConfig0 false -> OldConfig0
end end
end, OldConfig), end,
OldConfig
),
{ok, NewConfig}; {ok, NewConfig};
do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) -> do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) ->
case split_by_id(AuthenticatorID, OldConfig) of case split_by_id(AuthenticatorID, OldConfig) of
{error, Reason} -> {error, Reason}; {error, Reason} ->
{error, Reason};
{ok, BeforeFound, [Found | AfterFound]} -> {ok, BeforeFound, [Found | AfterFound]} ->
case Position of case Position of
?CMD_MOVE_FRONT -> ?CMD_MOVE_FRONT ->
@ -110,13 +121,14 @@ do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}
end end
end. end.
-spec post_config_update(list(atom()), -spec post_config_update(
update_request(), list(atom()),
map() | list(), update_request(),
emqx_config:raw_config(), map() | list(),
emqx_config:app_envs() emqx_config:raw_config(),
) emqx_config:app_envs()
-> ok | {ok, map()} | {error, term()}. ) ->
ok | {ok, map()} | {error, term()}.
post_config_update(_, UpdateReq, NewConfig, OldConfig, AppEnvs) -> post_config_update(_, UpdateReq, NewConfig, OldConfig, AppEnvs) ->
do_post_config_update(UpdateReq, check_configs(to_list(NewConfig)), OldConfig, AppEnvs). do_post_config_update(UpdateReq, check_configs(to_list(NewConfig)), OldConfig, AppEnvs).
@ -124,8 +136,12 @@ do_post_config_update({create_authenticator, ChainName, Config}, NewConfig, _Old
NConfig = get_authenticator_config(authenticator_id(Config), NewConfig), NConfig = get_authenticator_config(authenticator_id(Config), NewConfig),
_ = emqx_authentication:create_chain(ChainName), _ = emqx_authentication:create_chain(ChainName),
emqx_authentication:create_authenticator(ChainName, NConfig); emqx_authentication:create_authenticator(ChainName, NConfig);
do_post_config_update({delete_authenticator, ChainName, AuthenticatorID}, do_post_config_update(
_NewConfig, OldConfig, _AppEnvs) -> {delete_authenticator, ChainName, AuthenticatorID},
_NewConfig,
OldConfig,
_AppEnvs
) ->
case emqx_authentication:delete_authenticator(ChainName, AuthenticatorID) of case emqx_authentication:delete_authenticator(ChainName, AuthenticatorID) of
ok -> ok ->
Config = get_authenticator_config(AuthenticatorID, to_list(OldConfig)), Config = get_authenticator_config(AuthenticatorID, to_list(OldConfig)),
@ -134,16 +150,24 @@ do_post_config_update({delete_authenticator, ChainName, AuthenticatorID},
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
end; end;
do_post_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, do_post_config_update(
NewConfig, _OldConfig, _AppEnvs) -> {update_authenticator, ChainName, AuthenticatorID, Config},
NewConfig,
_OldConfig,
_AppEnvs
) ->
case get_authenticator_config(authenticator_id(Config), NewConfig) of case get_authenticator_config(authenticator_id(Config), NewConfig) of
{error, not_found} -> {error, not_found} ->
{error, {not_found, {authenticator, AuthenticatorID}}}; {error, {not_found, {authenticator, AuthenticatorID}}};
NConfig -> NConfig ->
emqx_authentication:update_authenticator(ChainName, AuthenticatorID, NConfig) emqx_authentication:update_authenticator(ChainName, AuthenticatorID, NConfig)
end; end;
do_post_config_update({move_authenticator, ChainName, AuthenticatorID, Position}, do_post_config_update(
_NewConfig, _OldConfig, _AppEnvs) -> {move_authenticator, ChainName, AuthenticatorID, Position},
_NewConfig,
_OldConfig,
_AppEnvs
) ->
emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position). emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position).
check_configs(Configs) -> check_configs(Configs) ->
@ -154,38 +178,45 @@ do_check_config(Config, Providers) ->
Type = authn_type(Config), Type = authn_type(Config),
case maps:get(Type, Providers, false) of case maps:get(Type, Providers, false) of
false -> false ->
?SLOG(warning, #{msg => "unknown_authn_type", ?SLOG(warning, #{
type => Type, msg => "unknown_authn_type",
providers => Providers}), type => Type,
providers => Providers
}),
throw({unknown_authn_type, Type}); throw({unknown_authn_type, Type});
Module -> Module ->
do_check_config(Type, Config, Module) do_check_config(Type, Config, Module)
end. end.
do_check_config(Type, Config, Module) -> do_check_config(Type, Config, Module) ->
F = case erlang:function_exported(Module, check_config, 1) of F =
case erlang:function_exported(Module, check_config, 1) of
true -> true ->
fun Module:check_config/1; fun Module:check_config/1;
false -> false ->
fun(C) -> fun(C) ->
Key = list_to_binary(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME), Key = list_to_binary(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME),
AtomKey = list_to_atom(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME), AtomKey = list_to_atom(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME),
R = hocon_tconf:check_plain(Module, #{Key => C}, R = hocon_tconf:check_plain(
#{atom_key => true}), Module,
maps:get(AtomKey, R) #{Key => C},
#{atom_key => true}
),
maps:get(AtomKey, R)
end end
end, end,
try try
F(Config) F(Config)
catch catch
C : E : S -> C:E:S ->
?SLOG(warning, #{msg => "failed_to_check_config", ?SLOG(warning, #{
config => Config, msg => "failed_to_check_config",
type => Type, config => Config,
exception => C, type => Type,
reason => E, exception => C,
stacktrace => S reason => E,
}), stacktrace => S
}),
throw({bad_authenticator_config, #{type => Type, reason => E}}) throw({bad_authenticator_config, #{type => Type, reason => E}})
end. end.
@ -232,17 +263,23 @@ get_authenticator_config(AuthenticatorID, AuthenticatorsConfig) ->
end. end.
split_by_id(ID, AuthenticatorsConfig) -> split_by_id(ID, AuthenticatorsConfig) ->
case lists:foldl( case
fun(C, {P1, P2, F0}) -> lists:foldl(
F = case ID =:= authenticator_id(C) of fun(C, {P1, P2, F0}) ->
true -> true; F =
false -> F0 case ID =:= authenticator_id(C) of
end, true -> true;
case F of false -> F0
false -> {[C | P1], P2, F}; end,
true -> {P1, [C | P2], F} case F of
end false -> {[C | P1], P2, F};
end, {[], [], false}, AuthenticatorsConfig) of true -> {P1, [C | P2], F}
end
end,
{[], [], false},
AuthenticatorsConfig
)
of
{_, _, false} -> {_, _, false} ->
{error, {not_found, {authenticator, ID}}}; {error, {not_found, {authenticator, ID}}};
{Part1, Part2, true} -> {Part1, Part2, true} ->
@ -273,7 +310,7 @@ authenticator_id(_C) ->
throw({missing_parameter, #{name => mechanism}}). throw({missing_parameter, #{name => mechanism}}).
%% @doc Make the authentication type. %% @doc Make the authentication type.
authn_type(#{mechanism := M, backend := B}) -> {atom(M), atom(B)}; authn_type(#{mechanism := M, backend := B}) -> {atom(M), atom(B)};
authn_type(#{mechanism := M}) -> atom(M); authn_type(#{mechanism := M}) -> atom(M);
authn_type(#{<<"mechanism">> := M, <<"backend">> := B}) -> {atom(M), atom(B)}; authn_type(#{<<"mechanism">> := M, <<"backend">> := B}) -> {atom(M), atom(B)};
authn_type(#{<<"mechanism">> := M}) -> atom(M). authn_type(#{<<"mechanism">> := M}) -> atom(M).

View File

@ -34,15 +34,19 @@ start_link() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
SupFlags = #{strategy => one_for_one, SupFlags = #{
intensity => 100, strategy => one_for_one,
period => 10}, intensity => 100,
period => 10
},
AuthN = #{id => emqx_authentication, AuthN = #{
start => {emqx_authentication, start_link, []}, id => emqx_authentication,
restart => permanent, start => {emqx_authentication, start_link, []},
shutdown => 1000, restart => permanent,
type => worker, shutdown => 1000,
modules => [emqx_authentication]}, type => worker,
modules => [emqx_authentication]
},
{ok, {SupFlags, [AuthN]}}. {ok, {SupFlags, [AuthN]}}.

View File

@ -18,52 +18,54 @@
-include("emqx.hrl"). -include("emqx.hrl").
-export([ list_authz_cache/0 -export([
, get_authz_cache/2 list_authz_cache/0,
, put_authz_cache/3 get_authz_cache/2,
, cleanup_authz_cache/0 put_authz_cache/3,
, empty_authz_cache/0 cleanup_authz_cache/0,
, dump_authz_cache/0 empty_authz_cache/0,
, get_cache_max_size/0 dump_authz_cache/0,
, get_cache_ttl/0 get_cache_max_size/0,
, is_enabled/0 get_cache_ttl/0,
, drain_cache/0 is_enabled/0,
, drain_cache/1 drain_cache/0,
]). drain_cache/1
]).
%% export for test %% export for test
-export([ cache_k/2 -export([
, cache_v/1 cache_k/2,
, get_cache_size/0 cache_v/1,
, get_newest_key/0 get_cache_size/0,
, get_oldest_key/0 get_newest_key/0,
]). get_oldest_key/0
]).
-type(authz_result() :: allow | deny). -type authz_result() :: allow | deny.
-type(system_time() :: integer()). -type system_time() :: integer().
-type(cache_key() :: {emqx_types:pubsub(), emqx_types:topic()}). -type cache_key() :: {emqx_types:pubsub(), emqx_types:topic()}.
-type(cache_val() :: {authz_result(), system_time()}). -type cache_val() :: {authz_result(), system_time()}.
-type(authz_cache_entry() :: {cache_key(), cache_val()}). -type authz_cache_entry() :: {cache_key(), cache_val()}.
%% Wrappers for key and value %% Wrappers for key and value
cache_k(PubSub, Topic)-> {PubSub, Topic}. cache_k(PubSub, Topic) -> {PubSub, Topic}.
cache_v(AuthzResult)-> {AuthzResult, time_now()}. cache_v(AuthzResult) -> {AuthzResult, time_now()}.
drain_k() -> {?MODULE, drain_timestamp}. drain_k() -> {?MODULE, drain_timestamp}.
-spec(is_enabled() -> boolean()). -spec is_enabled() -> boolean().
is_enabled() -> is_enabled() ->
emqx:get_config([authorization, cache, enable], false). emqx:get_config([authorization, cache, enable], false).
-spec(get_cache_max_size() -> integer()). -spec get_cache_max_size() -> integer().
get_cache_max_size() -> get_cache_max_size() ->
emqx:get_config([authorization, cache, max_size]). emqx:get_config([authorization, cache, max_size]).
-spec(get_cache_ttl() -> integer()). -spec get_cache_ttl() -> integer().
get_cache_ttl() -> get_cache_ttl() ->
emqx:get_config([authorization, cache, ttl]). emqx:get_config([authorization, cache, ttl]).
-spec(list_authz_cache() -> [authz_cache_entry()]). -spec list_authz_cache() -> [authz_cache_entry()].
list_authz_cache() -> list_authz_cache() ->
cleanup_authz_cache(), cleanup_authz_cache(),
map_authz_cache(fun(Cache) -> Cache end). map_authz_cache(fun(Cache) -> Cache end).
@ -73,23 +75,29 @@ list_authz_cache() ->
authz_result() | not_found. authz_result() | not_found.
get_authz_cache(PubSub, Topic) -> get_authz_cache(PubSub, Topic) ->
case erlang:get(cache_k(PubSub, Topic)) of case erlang:get(cache_k(PubSub, Topic)) of
undefined -> not_found; undefined ->
not_found;
{AuthzResult, CachedAt} -> {AuthzResult, CachedAt} ->
if_expired(get_cache_ttl(), CachedAt, if_expired(
fun(false) -> get_cache_ttl(),
AuthzResult; CachedAt,
(true) -> fun
cleanup_authz_cache(), (false) ->
not_found AuthzResult;
end) (true) ->
cleanup_authz_cache(),
not_found
end
)
end. end.
%% If the cache get full, and also the latest one %% If the cache get full, and also the latest one
%% is expired, then delete all the cache entries %% is expired, then delete all the cache entries
-spec put_authz_cache(emqx_types:pubsub(), emqx_types:topic(), authz_result()) -spec put_authz_cache(emqx_types:pubsub(), emqx_types:topic(), authz_result()) ->
-> ok. ok.
put_authz_cache(PubSub, Topic, AuthzResult) -> put_authz_cache(PubSub, Topic, AuthzResult) ->
MaxSize = get_cache_max_size(), true = (MaxSize =/= 0), MaxSize = get_cache_max_size(),
true = (MaxSize =/= 0),
Size = get_cache_size(), Size = get_cache_size(),
case Size < MaxSize of case Size < MaxSize of
true -> true ->
@ -97,37 +105,42 @@ put_authz_cache(PubSub, Topic, AuthzResult) ->
false -> false ->
NewestK = get_newest_key(), NewestK = get_newest_key(),
{_AuthzResult, CachedAt} = erlang:get(NewestK), {_AuthzResult, CachedAt} = erlang:get(NewestK),
if_expired(get_cache_ttl(), CachedAt, if_expired(
fun(true) -> get_cache_ttl(),
% all cache expired, cleanup first CachedAt,
empty_authz_cache(), fun
add_authz(PubSub, Topic, AuthzResult); (true) ->
(false) -> % all cache expired, cleanup first
% cache full, perform cache replacement empty_authz_cache(),
evict_authz_cache(), add_authz(PubSub, Topic, AuthzResult);
add_authz(PubSub, Topic, AuthzResult) (false) ->
end) % cache full, perform cache replacement
evict_authz_cache(),
add_authz(PubSub, Topic, AuthzResult)
end
)
end. end.
%% delete all the authz entries %% delete all the authz entries
-spec(empty_authz_cache() -> ok). -spec empty_authz_cache() -> ok.
empty_authz_cache() -> empty_authz_cache() ->
foreach_authz_cache(fun({CacheK, _CacheV}) -> erlang:erase(CacheK) end), foreach_authz_cache(fun({CacheK, _CacheV}) -> erlang:erase(CacheK) end),
set_cache_size(0), set_cache_size(0),
keys_queue_set(queue:new()). keys_queue_set(queue:new()).
%% delete the oldest authz entry %% delete the oldest authz entry
-spec(evict_authz_cache() -> ok). -spec evict_authz_cache() -> ok.
evict_authz_cache() -> evict_authz_cache() ->
OldestK = keys_queue_out(), OldestK = keys_queue_out(),
erlang:erase(OldestK), erlang:erase(OldestK),
decr_cache_size(). decr_cache_size().
%% cleanup all the expired cache entries %% cleanup all the expired cache entries
-spec(cleanup_authz_cache() -> ok). -spec cleanup_authz_cache() -> ok.
cleanup_authz_cache() -> cleanup_authz_cache() ->
keys_queue_set( keys_queue_set(
cleanup_authz(get_cache_ttl(), keys_queue_get())). cleanup_authz(get_cache_ttl(), keys_queue_get())
).
get_oldest_key() -> get_oldest_key() ->
keys_queue_pick(queue_front()). keys_queue_pick(queue_front()).
@ -144,8 +157,11 @@ dump_authz_cache() ->
map_authz_cache(fun(Cache) -> Cache end). map_authz_cache(fun(Cache) -> Cache end).
map_authz_cache(Fun) -> map_authz_cache(Fun) ->
[Fun(R) || R = {{SubPub, _T}, _Authz} <- erlang:get(), [
SubPub =:= publish orelse SubPub =:= subscribe]. Fun(R)
|| R = {{SubPub, _T}, _Authz} <- erlang:get(),
SubPub =:= publish orelse SubPub =:= subscribe
].
foreach_authz_cache(Fun) -> foreach_authz_cache(Fun) ->
_ = map_authz_cache(Fun), _ = map_authz_cache(Fun),
ok. ok.
@ -174,8 +190,7 @@ add_authz(PubSub, Topic, AuthzResult) ->
V = cache_v(AuthzResult), V = cache_v(AuthzResult),
case erlang:get(K) of case erlang:get(K) of
undefined -> add_new_authz(K, V); undefined -> add_new_authz(K, V);
{_AuthzResult, _CachedAt} -> {_AuthzResult, _CachedAt} -> update_authz(K, V)
update_authz(K, V)
end. end.
add_new_authz(K, V) -> add_new_authz(K, V) ->
@ -191,30 +206,38 @@ cleanup_authz(TTL, KeysQ) ->
case queue:out(KeysQ) of case queue:out(KeysQ) of
{{value, OldestK}, KeysQ2} -> {{value, OldestK}, KeysQ2} ->
{_AuthzResult, CachedAt} = erlang:get(OldestK), {_AuthzResult, CachedAt} = erlang:get(OldestK),
if_expired(TTL, CachedAt, if_expired(
fun(false) -> KeysQ; TTL,
(true) -> CachedAt,
erlang:erase(OldestK), fun
decr_cache_size(), (false) ->
cleanup_authz(TTL, KeysQ2) KeysQ;
end); (true) ->
{empty, KeysQ} -> KeysQ erlang:erase(OldestK),
decr_cache_size(),
cleanup_authz(TTL, KeysQ2)
end
);
{empty, KeysQ} ->
KeysQ
end. end.
incr_cache_size() -> incr_cache_size() ->
erlang:put(authz_cache_size, get_cache_size() + 1), ok. erlang:put(authz_cache_size, get_cache_size() + 1),
ok.
decr_cache_size() -> decr_cache_size() ->
Size = get_cache_size(), Size = get_cache_size(),
case Size > 1 of case Size > 1 of
true -> true ->
erlang:put(authz_cache_size, Size-1); erlang:put(authz_cache_size, Size - 1);
false -> false ->
erlang:put(authz_cache_size, 0) erlang:put(authz_cache_size, 0)
end, end,
ok. ok.
set_cache_size(N) -> set_cache_size(N) ->
erlang:put(authz_cache_size, N), ok. erlang:put(authz_cache_size, N),
ok.
%%% Ordered Keys Q %%% %%% Ordered Keys Q %%%
keys_queue_in(Key) -> keys_queue_in(Key) ->
@ -225,7 +248,8 @@ keys_queue_in(Key) ->
keys_queue_out() -> keys_queue_out() ->
case queue:out(keys_queue_get()) of case queue:out(keys_queue_get()) of
{{value, OldestK}, Q2} -> {{value, OldestK}, Q2} ->
keys_queue_set(Q2), OldestK; keys_queue_set(Q2),
OldestK;
{empty, _Q} -> {empty, _Q} ->
undefined undefined
end. end.
@ -242,12 +266,17 @@ keys_queue_pick(Pick) ->
end. end.
keys_queue_remove(Key, KeysQ) -> keys_queue_remove(Key, KeysQ) ->
queue:filter(fun queue:filter(
(K) when K =:= Key -> false; (_) -> true fun
end, KeysQ). (K) when K =:= Key -> false;
(_) -> true
end,
KeysQ
).
keys_queue_set(KeysQ) -> keys_queue_set(KeysQ) ->
erlang:put(authz_keys_q, KeysQ), ok. erlang:put(authz_keys_q, KeysQ),
ok.
keys_queue_get() -> keys_queue_get() ->
case erlang:get(authz_keys_q) of case erlang:get(authz_keys_q) of
undefined -> queue:new(); undefined -> queue:new();

View File

@ -22,7 +22,6 @@
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
%% Mnesia bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
@ -30,23 +29,25 @@
-export([start_link/0, stop/0]). -export([start_link/0, stop/0]).
-export([ check/1 -export([
, create/1 check/1,
, look_up/1 create/1,
, delete/1 look_up/1,
, info/1 delete/1,
, format/1 info/1,
, parse/1 format/1,
]). parse/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-elvis([{elvis_style, state_record_and_type, disable}]). -elvis([{elvis_style, state_record_and_type, disable}]).
@ -63,68 +64,71 @@
mnesia(boot) -> mnesia(boot) ->
ok = mria:create_table(?BANNED_TAB, [ ok = mria:create_table(?BANNED_TAB, [
{type, set}, {type, set},
{rlog_shard, ?COMMON_SHARD}, {rlog_shard, ?COMMON_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, banned}, {record_name, banned},
{attributes, record_info(fields, banned)}, {attributes, record_info(fields, banned)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}]). {storage_properties, [{ets, [{read_concurrency, true}]}]}
]).
%% @doc Start the banned server. %% @doc Start the banned server.
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% for tests %% for tests
-spec(stop() -> ok). -spec stop() -> ok.
stop() -> gen_server:stop(?MODULE). stop() -> gen_server:stop(?MODULE).
-spec(check(emqx_types:clientinfo()) -> boolean()). -spec check(emqx_types:clientinfo()) -> boolean().
check(ClientInfo) -> check(ClientInfo) ->
do_check({clientid, maps:get(clientid, ClientInfo, undefined)}) do_check({clientid, maps:get(clientid, ClientInfo, undefined)}) orelse
orelse do_check({username, maps:get(username, ClientInfo, undefined)}) do_check({username, maps:get(username, ClientInfo, undefined)}) orelse
orelse do_check({peerhost, maps:get(peerhost, ClientInfo, undefined)}). do_check({peerhost, maps:get(peerhost, ClientInfo, undefined)}).
do_check({_, undefined}) -> do_check({_, undefined}) ->
false; false;
do_check(Who) when is_tuple(Who) -> do_check(Who) when is_tuple(Who) ->
case mnesia:dirty_read(?BANNED_TAB, Who) of case mnesia:dirty_read(?BANNED_TAB, Who) of
[] -> false; [] -> false;
[#banned{until = Until}] -> [#banned{until = Until}] -> Until > erlang:system_time(second)
Until > erlang:system_time(second)
end. end.
format(#banned{who = Who0, format(#banned{
by = By, who = Who0,
reason = Reason, by = By,
at = At, reason = Reason,
until = Until}) -> at = At,
until = Until
}) ->
{As, Who} = maybe_format_host(Who0), {As, Who} = maybe_format_host(Who0),
#{ #{
as => As, as => As,
who => Who, who => Who,
by => By, by => By,
reason => Reason, reason => Reason,
at => to_rfc3339(At), at => to_rfc3339(At),
until => to_rfc3339(Until) until => to_rfc3339(Until)
}. }.
parse(Params) -> parse(Params) ->
case pares_who(Params) of case pares_who(Params) of
{error, Reason} -> {error, Reason}; {error, Reason} ->
Who -> {error, Reason};
By = maps:get(<<"by">>, Params, <<"mgmt_api">>), Who ->
By = maps:get(<<"by">>, Params, <<"mgmt_api">>),
Reason = maps:get(<<"reason">>, Params, <<"">>), Reason = maps:get(<<"reason">>, Params, <<"">>),
At = maps:get(<<"at">>, Params, erlang:system_time(second)), At = maps:get(<<"at">>, Params, erlang:system_time(second)),
Until = maps:get(<<"until">>, Params, At + 5 * 60), Until = maps:get(<<"until">>, Params, At + 5 * 60),
case Until > erlang:system_time(second) of case Until > erlang:system_time(second) of
true -> true ->
#banned{ #banned{
who = Who, who = Who,
by = By, by = By,
reason = Reason, reason = Reason,
at = At, at = At,
until = Until until = Until
}; };
false -> false ->
ErrorReason = ErrorReason =
@ -151,13 +155,15 @@ maybe_format_host({As, Who}) ->
to_rfc3339(Timestamp) -> to_rfc3339(Timestamp) ->
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, second}])). list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, second}])).
-spec(create(emqx_types:banned() | map()) -> -spec create(emqx_types:banned() | map()) ->
{ok, emqx_types:banned()} | {error, {already_exist, emqx_types:banned()}}). {ok, emqx_types:banned()} | {error, {already_exist, emqx_types:banned()}}.
create(#{who := Who, create(#{
by := By, who := Who,
reason := Reason, by := By,
at := At, reason := Reason,
until := Until}) -> at := At,
until := Until
}) ->
Banned = #banned{ Banned = #banned{
who = Who, who = Who,
by = By, by = By,
@ -166,8 +172,7 @@ create(#{who := Who,
until = Until until = Until
}, },
create(Banned); create(Banned);
create(Banned = #banned{who = Who}) ->
create(Banned = #banned{who = Who}) ->
case look_up(Who) of case look_up(Who) of
[] -> [] ->
mria:dirty_write(?BANNED_TAB, Banned), mria:dirty_write(?BANNED_TAB, Banned),
@ -176,8 +181,10 @@ create(Banned = #banned{who = Who}) ->
%% Don't support shorten or extend the until time by overwrite. %% Don't support shorten or extend the until time by overwrite.
%% We don't support update api yet, user must delete then create new one. %% We don't support update api yet, user must delete then create new one.
case Until > erlang:system_time(second) of case Until > erlang:system_time(second) of
true -> {error, {already_exist, OldBanned}}; true ->
false -> %% overwrite expired one is ok. {error, {already_exist, OldBanned}};
%% overwrite expired one is ok.
false ->
mria:dirty_write(?BANNED_TAB, Banned), mria:dirty_write(?BANNED_TAB, Banned),
{ok, Banned} {ok, Banned}
end end
@ -188,10 +195,12 @@ look_up(Who) when is_map(Who) ->
look_up(Who) -> look_up(Who) ->
mnesia:dirty_read(?BANNED_TAB, Who). mnesia:dirty_read(?BANNED_TAB, Who).
-spec(delete({clientid, emqx_types:clientid()} -spec delete(
| {username, emqx_types:username()} {clientid, emqx_types:clientid()}
| {peerhost, emqx_types:peerhost()}) -> ok). | {username, emqx_types:username()}
delete(Who) when is_map(Who)-> | {peerhost, emqx_types:peerhost()}
) -> ok.
delete(Who) when is_map(Who) ->
delete(pares_who(Who)); delete(pares_who(Who));
delete(Who) -> delete(Who) ->
mria:dirty_delete(?BANNED_TAB, Who). mria:dirty_delete(?BANNED_TAB, Who).
@ -217,7 +226,6 @@ handle_cast(Msg, State) ->
handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) -> handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) ->
_ = mria:transaction(?COMMON_SHARD, fun expire_banned_items/1, [erlang:system_time(second)]), _ = mria:transaction(?COMMON_SHARD, fun expire_banned_items/1, [erlang:system_time(second)]),
{noreply, ensure_expiry_timer(State), hibernate}; {noreply, ensure_expiry_timer(State), hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -242,7 +250,12 @@ ensure_expiry_timer(State) ->
expire_banned_items(Now) -> expire_banned_items(Now) ->
mnesia:foldl( mnesia:foldl(
fun(B = #banned{until = Until}, _Acc) when Until < Now -> fun
mnesia:delete_object(?BANNED_TAB, B, sticky_write); (B = #banned{until = Until}, _Acc) when Until < Now ->
(_, _Acc) -> ok mnesia:delete_object(?BANNED_TAB, B, sticky_write);
end, ok, ?BANNED_TAB). (_, _Acc) ->
ok
end,
ok,
?BANNED_TAB
).

View File

@ -17,9 +17,10 @@
-module(emqx_base62). -module(emqx_base62).
%% APIs %% APIs
-export([ encode/1 -export([
, decode/1 encode/1,
]). decode/1
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
@ -29,7 +30,7 @@
-spec encode(string() | integer() | binary()) -> binary(). -spec encode(string() | integer() | binary()) -> binary().
encode(I) when is_integer(I) -> encode(I) when is_integer(I) ->
encode(integer_to_binary(I)); encode(integer_to_binary(I));
encode(S) when is_list(S)-> encode(S) when is_list(S) ->
encode(unicode:characters_to_binary(S)); encode(unicode:characters_to_binary(S));
encode(B) when is_binary(B) -> encode(B) when is_binary(B) ->
encode(B, <<>>). encode(B, <<>>).
@ -47,21 +48,22 @@ decode(B) when is_binary(B) ->
encode(<<Index1:6, Index2:6, Index3:6, Index4:6, Rest/binary>>, Acc) -> encode(<<Index1:6, Index2:6, Index3:6, Index4:6, Rest/binary>>, Acc) ->
CharList = [encode_char(Index1), encode_char(Index2), encode_char(Index3), encode_char(Index4)], CharList = [encode_char(Index1), encode_char(Index2), encode_char(Index3), encode_char(Index4)],
NewAcc = <<Acc/binary,(iolist_to_binary(CharList))/binary>>, NewAcc = <<Acc/binary, (iolist_to_binary(CharList))/binary>>,
encode(Rest, NewAcc); encode(Rest, NewAcc);
encode(<<Index1:6, Index2:6, Index3:4>>, Acc) -> encode(<<Index1:6, Index2:6, Index3:4>>, Acc) ->
CharList = [encode_char(Index1), encode_char(Index2), encode_char(Index3)], CharList = [encode_char(Index1), encode_char(Index2), encode_char(Index3)],
NewAcc = <<Acc/binary,(iolist_to_binary(CharList))/binary>>, NewAcc = <<Acc/binary, (iolist_to_binary(CharList))/binary>>,
encode(<<>>, NewAcc); encode(<<>>, NewAcc);
encode(<<Index1:6, Index2:2>>, Acc) -> encode(<<Index1:6, Index2:2>>, Acc) ->
CharList = [encode_char(Index1), encode_char(Index2)], CharList = [encode_char(Index1), encode_char(Index2)],
NewAcc = <<Acc/binary,(iolist_to_binary(CharList))/binary>>, NewAcc = <<Acc/binary, (iolist_to_binary(CharList))/binary>>,
encode(<<>>, NewAcc); encode(<<>>, NewAcc);
encode(<<>>, Acc) -> encode(<<>>, Acc) ->
Acc. Acc.
decode(<<Head:8, Rest/binary>>, Acc) decode(<<Head:8, Rest/binary>>, Acc) when
when bit_size(Rest) >= 8-> bit_size(Rest) >= 8
->
case Head == $9 of case Head == $9 of
true -> true ->
<<Head1:8, Rest1/binary>> = Rest, <<Head1:8, Rest1/binary>> = Rest,
@ -85,7 +87,6 @@ decode(<<Head:8, Rest/binary>>, Acc) ->
decode(<<>>, Acc) -> decode(<<>>, Acc) ->
Acc. Acc.
encode_char(I) when I < 26 -> encode_char(I) when I < 26 ->
$A + I; $A + I;
encode_char(I) when I < 52 -> encode_char(I) when I < 52 ->
@ -97,9 +98,9 @@ encode_char(I) ->
decode_char(I) when I >= $a andalso I =< $z -> decode_char(I) when I >= $a andalso I =< $z ->
I + 26 - $a; I + 26 - $a;
decode_char(I) when I >= $0 andalso I =< $8-> decode_char(I) when I >= $0 andalso I =< $8 ->
I + 52 - $0; I + 52 - $0;
decode_char(I) when I >= $A andalso I =< $Z-> decode_char(I) when I >= $A andalso I =< $Z ->
I - $A. I - $A.
decode_char(9, I) -> decode_char(9, I) ->

View File

@ -17,62 +17,69 @@
-module(emqx_batch). -module(emqx_batch).
%% APIs %% APIs
-export([ init/1 -export([
, push/2 init/1,
, commit/1 push/2,
, size/1 commit/1,
, items/1 size/1,
]). items/1
]).
-export_type([options/0, batch/0]). -export_type([options/0, batch/0]).
-record(batch, { -record(batch, {
batch_size :: non_neg_integer(), batch_size :: non_neg_integer(),
batch_q :: list(any()), batch_q :: list(any()),
linger_ms :: pos_integer(), linger_ms :: pos_integer(),
linger_timer :: reference() | undefined, linger_timer :: reference() | undefined,
commit_fun :: function() commit_fun :: function()
}). }).
-type(options() :: #{ -type options() :: #{
batch_size => non_neg_integer(), batch_size => non_neg_integer(),
linger_ms => pos_integer(), linger_ms => pos_integer(),
commit_fun := function() commit_fun := function()
}). }.
-opaque(batch() :: #batch{}). -opaque batch() :: #batch{}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(init(options()) -> batch()). -spec init(options()) -> batch().
init(Opts) when is_map(Opts) -> init(Opts) when is_map(Opts) ->
#batch{batch_size = maps:get(batch_size, Opts, 1000), #batch{
batch_q = [], batch_size = maps:get(batch_size, Opts, 1000),
linger_ms = maps:get(linger_ms, Opts, 1000), batch_q = [],
commit_fun = maps:get(commit_fun, Opts)}. linger_ms = maps:get(linger_ms, Opts, 1000),
commit_fun = maps:get(commit_fun, Opts)
}.
-spec(push(any(), batch()) -> batch()). -spec push(any(), batch()) -> batch().
push(El, Batch = #batch{batch_q = Q, push(
linger_ms = Ms, El,
linger_timer = undefined}) Batch = #batch{
when length(Q) == 0 -> batch_q = Q,
linger_ms = Ms,
linger_timer = undefined
}
) when
length(Q) == 0
->
TRef = erlang:send_after(Ms, self(), batch_linger_expired), TRef = erlang:send_after(Ms, self(), batch_linger_expired),
Batch#batch{batch_q = [El], linger_timer = TRef}; Batch#batch{batch_q = [El], linger_timer = TRef};
%% no limit. %% no limit.
push(El, Batch = #batch{batch_size = 0, batch_q = Q}) -> push(El, Batch = #batch{batch_size = 0, batch_q = Q}) ->
Batch#batch{batch_q = [El|Q]}; Batch#batch{batch_q = [El | Q]};
push(El, Batch = #batch{batch_size = MaxSize, batch_q = Q}) when
push(El, Batch = #batch{batch_size = MaxSize, batch_q = Q}) length(Q) >= MaxSize
when length(Q) >= MaxSize -> ->
commit(Batch#batch{batch_q = [El|Q]}); commit(Batch#batch{batch_q = [El | Q]});
push(El, Batch = #batch{batch_q = Q}) -> push(El, Batch = #batch{batch_q = Q}) ->
Batch#batch{batch_q = [El|Q]}. Batch#batch{batch_q = [El | Q]}.
-spec(commit(batch()) -> batch()). -spec commit(batch()) -> batch().
commit(Batch = #batch{batch_q = Q, commit_fun = Commit}) -> commit(Batch = #batch{batch_q = Q, commit_fun = Commit}) ->
_ = Commit(lists:reverse(Q)), _ = Commit(lists:reverse(Q)),
reset(Batch). reset(Batch).
@ -81,11 +88,10 @@ reset(Batch = #batch{linger_timer = TRef}) ->
_ = emqx_misc:cancel_timer(TRef), _ = emqx_misc:cancel_timer(TRef),
Batch#batch{batch_q = [], linger_timer = undefined}. Batch#batch{batch_q = [], linger_timer = undefined}.
-spec(size(batch()) -> non_neg_integer()). -spec size(batch()) -> non_neg_integer().
size(#batch{batch_q = Q}) -> size(#batch{batch_q = Q}) ->
length(Q). length(Q).
-spec(items(batch()) -> list(any())). -spec items(batch()) -> list(any()).
items(#batch{batch_q = Q}) -> items(#batch{batch_q = Q}) ->
lists:reverse(Q). lists:reverse(Q).

View File

@ -20,10 +20,9 @@
-define(BOOT_MODULES, [router, broker, listeners]). -define(BOOT_MODULES, [router, broker, listeners]).
-spec(is_enabled(all|router|broker|listeners) -> boolean()). -spec is_enabled(all | router | broker | listeners) -> boolean().
is_enabled(Mod) -> is_enabled(Mod) ->
(BootMods = boot_modules()) =:= all orelse lists:member(Mod, BootMods). (BootMods = boot_modules()) =:= all orelse lists:member(Mod, BootMods).
boot_modules() -> boot_modules() ->
application:get_env(emqx, boot_modules, ?BOOT_MODULES). application:get_env(emqx, boot_modules, ?BOOT_MODULES).

View File

@ -23,35 +23,38 @@
-include("types.hrl"). -include("types.hrl").
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-export([start_link/2]). -export([start_link/2]).
%% PubSub %% PubSub
-export([ subscribe/1 -export([
, subscribe/2 subscribe/1,
, subscribe/3 subscribe/2,
]). subscribe/3
]).
-export([unsubscribe/1]). -export([unsubscribe/1]).
-export([subscriber_down/1]). -export([subscriber_down/1]).
-export([ publish/1 -export([
, safe_publish/1 publish/1,
]). safe_publish/1
]).
-export([dispatch/2]). -export([dispatch/2]).
%% PubSub Infos %% PubSub Infos
-export([ subscriptions/1 -export([
, subscriptions_via_topic/1 subscriptions/1,
, subscribers/1 subscriptions_via_topic/1,
, subscribed/2 subscribers/1,
]). subscribed/2
]).
-export([ get_subopts/2 -export([
, set_subopts/2 get_subopts/2,
]). set_subopts/2
]).
-export([topics/0]). -export([topics/0]).
@ -59,13 +62,14 @@
-export([stats_fun/0]). -export([stats_fun/0]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-import(emqx_tables, [lookup_value/2, lookup_value/3]). -import(emqx_tables, [lookup_value/2, lookup_value/3]).
@ -84,17 +88,21 @@
%% Guards %% Guards
-define(IS_SUBID(Id), (is_binary(Id) orelse is_atom(Id))). -define(IS_SUBID(Id), (is_binary(Id) orelse is_atom(Id))).
-spec(start_link(atom(), pos_integer()) -> startlink_ret()). -spec start_link(atom(), pos_integer()) -> startlink_ret().
start_link(Pool, Id) -> start_link(Pool, Id) ->
ok = create_tabs(), ok = create_tabs(),
gen_server:start_link({local, emqx_misc:proc_name(?BROKER, Id)}, gen_server:start_link(
?MODULE, [Pool, Id], []). {local, emqx_misc:proc_name(?BROKER, Id)},
?MODULE,
[Pool, Id],
[]
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Create tabs %% Create tabs
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec(create_tabs() -> ok). -spec create_tabs() -> ok.
create_tabs() -> create_tabs() ->
TabOpts = [public, {read_concurrency, true}, {write_concurrency, true}], TabOpts = [public, {read_concurrency, true}, {write_concurrency, true}],
@ -113,28 +121,31 @@ create_tabs() ->
%% Subscribe API %% Subscribe API
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec(subscribe(emqx_types:topic()) -> ok). -spec subscribe(emqx_types:topic()) -> ok.
subscribe(Topic) when is_binary(Topic) -> subscribe(Topic) when is_binary(Topic) ->
subscribe(Topic, undefined). subscribe(Topic, undefined).
-spec(subscribe(emqx_types:topic(), emqx_types:subid() | emqx_types:subopts()) -> ok). -spec subscribe(emqx_types:topic(), emqx_types:subid() | emqx_types:subopts()) -> ok.
subscribe(Topic, SubId) when is_binary(Topic), ?IS_SUBID(SubId) -> subscribe(Topic, SubId) when is_binary(Topic), ?IS_SUBID(SubId) ->
subscribe(Topic, SubId, ?DEFAULT_SUBOPTS); subscribe(Topic, SubId, ?DEFAULT_SUBOPTS);
subscribe(Topic, SubOpts) when is_binary(Topic), is_map(SubOpts) -> subscribe(Topic, SubOpts) when is_binary(Topic), is_map(SubOpts) ->
subscribe(Topic, undefined, SubOpts). subscribe(Topic, undefined, SubOpts).
-spec(subscribe(emqx_types:topic(), emqx_types:subid(), emqx_types:subopts()) -> ok). -spec subscribe(emqx_types:topic(), emqx_types:subid(), emqx_types:subopts()) -> ok.
subscribe(Topic, SubId, SubOpts0) when is_binary(Topic), ?IS_SUBID(SubId), is_map(SubOpts0) -> subscribe(Topic, SubId, SubOpts0) when is_binary(Topic), ?IS_SUBID(SubId), is_map(SubOpts0) ->
SubOpts = maps:merge(?DEFAULT_SUBOPTS, SubOpts0), SubOpts = maps:merge(?DEFAULT_SUBOPTS, SubOpts0),
_ = emqx_trace:subscribe(Topic, SubId, SubOpts), _ = emqx_trace:subscribe(Topic, SubId, SubOpts),
SubPid = self(), SubPid = self(),
case ets:member(?SUBOPTION, {SubPid, Topic}) of case ets:member(?SUBOPTION, {SubPid, Topic}) of
false -> %% New %% New
false ->
ok = emqx_broker_helper:register_sub(SubPid, SubId), ok = emqx_broker_helper:register_sub(SubPid, SubId),
do_subscribe(Topic, SubPid, with_subid(SubId, SubOpts)); do_subscribe(Topic, SubPid, with_subid(SubId, SubOpts));
true -> %% Existed %% Existed
true ->
set_subopts(SubPid, Topic, with_subid(SubId, SubOpts)), set_subopts(SubPid, Topic, with_subid(SubId, SubOpts)),
ok %% ensure to return 'ok' %% ensure to return 'ok'
ok
end. end.
-compile({inline, [with_subid/2]}). -compile({inline, [with_subid/2]}).
@ -151,14 +162,15 @@ do_subscribe(Topic, SubPid, SubOpts) ->
do_subscribe(undefined, Topic, SubPid, SubOpts) -> do_subscribe(undefined, Topic, SubPid, SubOpts) ->
case emqx_broker_helper:get_sub_shard(SubPid, Topic) of case emqx_broker_helper:get_sub_shard(SubPid, Topic) of
0 -> true = ets:insert(?SUBSCRIBER, {Topic, SubPid}), 0 ->
true = ets:insert(?SUBOPTION, {{SubPid, Topic}, SubOpts}), true = ets:insert(?SUBSCRIBER, {Topic, SubPid}),
call(pick(Topic), {subscribe, Topic}); true = ets:insert(?SUBOPTION, {{SubPid, Topic}, SubOpts}),
I -> true = ets:insert(?SUBSCRIBER, {{shard, Topic, I}, SubPid}), call(pick(Topic), {subscribe, Topic});
true = ets:insert(?SUBOPTION, {{SubPid, Topic}, maps:put(shard, I, SubOpts)}), I ->
call(pick({Topic, I}), {subscribe, Topic, I}) true = ets:insert(?SUBSCRIBER, {{shard, Topic, I}, SubPid}),
true = ets:insert(?SUBOPTION, {{SubPid, Topic}, maps:put(shard, I, SubOpts)}),
call(pick({Topic, I}), {subscribe, Topic, I})
end; end;
%% Shared subscription %% Shared subscription
do_subscribe(Group, Topic, SubPid, SubOpts) -> do_subscribe(Group, Topic, SubPid, SubOpts) ->
true = ets:insert(?SUBOPTION, {{SubPid, Topic}, SubOpts}), true = ets:insert(?SUBOPTION, {{SubPid, Topic}, SubOpts}),
@ -168,7 +180,7 @@ do_subscribe(Group, Topic, SubPid, SubOpts) ->
%% Unsubscribe API %% Unsubscribe API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(unsubscribe(emqx_types:topic()) -> ok). -spec unsubscribe(emqx_types:topic()) -> ok.
unsubscribe(Topic) when is_binary(Topic) -> unsubscribe(Topic) when is_binary(Topic) ->
SubPid = self(), SubPid = self(),
case ets:lookup(?SUBOPTION, {SubPid, Topic}) of case ets:lookup(?SUBOPTION, {SubPid, Topic}) of
@ -176,7 +188,8 @@ unsubscribe(Topic) when is_binary(Topic) ->
_ = emqx_broker_helper:reclaim_seq(Topic), _ = emqx_broker_helper:reclaim_seq(Topic),
_ = emqx_trace:unsubscribe(Topic, SubOpts), _ = emqx_trace:unsubscribe(Topic, SubOpts),
do_unsubscribe(Topic, SubPid, SubOpts); do_unsubscribe(Topic, SubPid, SubOpts);
[] -> ok [] ->
ok
end. end.
do_unsubscribe(Topic, SubPid, SubOpts) -> do_unsubscribe(Topic, SubPid, SubOpts) ->
@ -187,12 +200,13 @@ do_unsubscribe(Topic, SubPid, SubOpts) ->
do_unsubscribe(undefined, Topic, SubPid, SubOpts) -> do_unsubscribe(undefined, Topic, SubPid, SubOpts) ->
case maps:get(shard, SubOpts, 0) of case maps:get(shard, SubOpts, 0) of
0 -> true = ets:delete_object(?SUBSCRIBER, {Topic, SubPid}), 0 ->
cast(pick(Topic), {unsubscribed, Topic}); true = ets:delete_object(?SUBSCRIBER, {Topic, SubPid}),
I -> true = ets:delete_object(?SUBSCRIBER, {{shard, Topic, I}, SubPid}), cast(pick(Topic), {unsubscribed, Topic});
cast(pick({Topic, I}), {unsubscribed, Topic, I}) I ->
true = ets:delete_object(?SUBSCRIBER, {{shard, Topic, I}, SubPid}),
cast(pick({Topic, I}), {unsubscribed, Topic, I})
end; end;
do_unsubscribe(Group, Topic, SubPid, _SubOpts) -> do_unsubscribe(Group, Topic, SubPid, _SubOpts) ->
emqx_shared_sub:unsubscribe(Group, Topic, SubPid). emqx_shared_sub:unsubscribe(Group, Topic, SubPid).
@ -200,14 +214,16 @@ do_unsubscribe(Group, Topic, SubPid, _SubOpts) ->
%% Publish %% Publish
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(publish(emqx_types:message()) -> emqx_types:publish_result()). -spec publish(emqx_types:message()) -> emqx_types:publish_result().
publish(Msg) when is_record(Msg, message) -> publish(Msg) when is_record(Msg, message) ->
_ = emqx_trace:publish(Msg), _ = emqx_trace:publish(Msg),
emqx_message:is_sys(Msg) orelse emqx_metrics:inc('messages.publish'), emqx_message:is_sys(Msg) orelse emqx_metrics:inc('messages.publish'),
case emqx_hooks:run_fold('message.publish', [], emqx_message:clean_dup(Msg)) of case emqx_hooks:run_fold('message.publish', [], emqx_message:clean_dup(Msg)) of
#message{headers = #{allow_publish := false}, topic = Topic} -> #message{headers = #{allow_publish := false}, topic = Topic} ->
?TRACE("MQTT", "msg_publish_not_allowed", #{message => emqx_message:to_log_map(Msg), ?TRACE("MQTT", "msg_publish_not_allowed", #{
topic => Topic}), message => emqx_message:to_log_map(Msg),
topic => Topic
}),
[]; [];
Msg1 = #message{topic = Topic} -> Msg1 = #message{topic = Topic} ->
emqx_persistent_session:persist_message(Msg1), emqx_persistent_session:persist_message(Msg1),
@ -215,19 +231,21 @@ publish(Msg) when is_record(Msg, message) ->
end. end.
%% Called internally %% Called internally
-spec(safe_publish(emqx_types:message()) -> emqx_types:publish_result()). -spec safe_publish(emqx_types:message()) -> emqx_types:publish_result().
safe_publish(Msg) when is_record(Msg, message) -> safe_publish(Msg) when is_record(Msg, message) ->
try try
publish(Msg) publish(Msg)
catch catch
Error : Reason : Stk-> Error:Reason:Stk ->
?SLOG(error,#{ ?SLOG(
msg => "publishing_error", error,
exception => Error, #{
reason => Reason, msg => "publishing_error",
payload => emqx_message:to_log_map(Msg), exception => Error,
stacktrace => Stk reason => Reason,
}, payload => emqx_message:to_log_map(Msg),
stacktrace => Stk
},
#{topic => Msg#message.topic} #{topic => Msg#message.topic}
), ),
[] []
@ -240,17 +258,20 @@ delivery(Msg) -> #delivery{sender = self(), message = Msg}.
%% Route %% Route
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(route([emqx_types:route_entry()], emqx_types:delivery()) -spec route([emqx_types:route_entry()], emqx_types:delivery()) ->
-> emqx_types:publish_result()). emqx_types:publish_result().
route([], #delivery{message = Msg}) -> route([], #delivery{message = Msg}) ->
ok = emqx_hooks:run('message.dropped', [Msg, #{node => node()}, no_subscribers]), ok = emqx_hooks:run('message.dropped', [Msg, #{node => node()}, no_subscribers]),
ok = inc_dropped_cnt(Msg), ok = inc_dropped_cnt(Msg),
[]; [];
route(Routes, Delivery) -> route(Routes, Delivery) ->
lists:foldl(fun(Route, Acc) -> lists:foldl(
[do_route(Route, Delivery) | Acc] fun(Route, Acc) ->
end, [], Routes). [do_route(Route, Delivery) | Acc]
end,
[],
Routes
).
do_route({To, Node}, Delivery) when Node =:= node() -> do_route({To, Node}, Delivery) when Node =:= node() ->
{Node, To, dispatch(To, Delivery)}; {Node, To, dispatch(To, Delivery)};
@ -259,43 +280,52 @@ do_route({To, Node}, Delivery) when is_atom(Node) ->
do_route({To, Group}, Delivery) when is_tuple(Group); is_binary(Group) -> do_route({To, Group}, Delivery) when is_tuple(Group); is_binary(Group) ->
{share, To, emqx_shared_sub:dispatch(Group, To, Delivery)}. {share, To, emqx_shared_sub:dispatch(Group, To, Delivery)}.
aggre([]) -> []; aggre([]) ->
[];
aggre([#route{topic = To, dest = Node}]) when is_atom(Node) -> aggre([#route{topic = To, dest = Node}]) when is_atom(Node) ->
[{To, Node}]; [{To, Node}];
aggre([#route{topic = To, dest = {Group, _Node}}]) -> aggre([#route{topic = To, dest = {Group, _Node}}]) ->
[{To, Group}]; [{To, Group}];
aggre(Routes) -> aggre(Routes) ->
lists:foldl( lists:foldl(
fun(#route{topic = To, dest = Node}, Acc) when is_atom(Node) -> fun
[{To, Node} | Acc]; (#route{topic = To, dest = Node}, Acc) when is_atom(Node) ->
(#route{topic = To, dest = {Group, _Node}}, Acc) -> [{To, Node} | Acc];
lists:usort([{To, Group} | Acc]) (#route{topic = To, dest = {Group, _Node}}, Acc) ->
end, [], Routes). lists:usort([{To, Group} | Acc])
end,
[],
Routes
).
%% @doc Forward message to another node. %% @doc Forward message to another node.
-spec(forward(node(), emqx_types:topic(), emqx_types:delivery(), RpcMode::sync | async) -spec forward(node(), emqx_types:topic(), emqx_types:delivery(), RpcMode :: sync | async) ->
-> emqx_types:deliver_result()). emqx_types:deliver_result().
forward(Node, To, Delivery, async) -> forward(Node, To, Delivery, async) ->
true = emqx_broker_proto_v1:forward_async(Node, To, Delivery), true = emqx_broker_proto_v1:forward_async(Node, To, Delivery),
emqx_metrics:inc('messages.forward'); emqx_metrics:inc('messages.forward');
forward(Node, To, Delivery, sync) -> forward(Node, To, Delivery, sync) ->
case emqx_broker_proto_v1:forward(Node, To, Delivery) of case emqx_broker_proto_v1:forward(Node, To, Delivery) of
{Err, Reason} when Err =:= badrpc; Err =:= badtcp -> {Err, Reason} when Err =:= badrpc; Err =:= badtcp ->
?SLOG(error, #{ ?SLOG(
msg => "sync_forward_msg_to_node_failed", error,
node => Node, #{
Err => Reason msg => "sync_forward_msg_to_node_failed",
}, #{topic => To}), node => Node,
Err => Reason
},
#{topic => To}
),
{error, badrpc}; {error, badrpc};
Result -> Result ->
emqx_metrics:inc('messages.forward'), emqx_metrics:inc('messages.forward'),
Result Result
end. end.
-spec(dispatch(emqx_types:topic(), emqx_types:delivery()) -> emqx_types:deliver_result()). -spec dispatch(emqx_types:topic(), emqx_types:delivery()) -> emqx_types:deliver_result().
dispatch(Topic, Delivery = #delivery{}) when is_binary(Topic) -> dispatch(Topic, Delivery = #delivery{}) when is_binary(Topic) ->
case emqx:is_running() of case emqx:is_running() of
true -> true ->
do_dispatch(Topic, Delivery); do_dispatch(Topic, Delivery);
false -> false ->
%% In a rare case emqx_router_helper process may delay %% In a rare case emqx_router_helper process may delay
@ -308,81 +338,92 @@ dispatch(Topic, Delivery = #delivery{}) when is_binary(Topic) ->
-compile({inline, [inc_dropped_cnt/1]}). -compile({inline, [inc_dropped_cnt/1]}).
inc_dropped_cnt(Msg) -> inc_dropped_cnt(Msg) ->
case emqx_message:is_sys(Msg) of case emqx_message:is_sys(Msg) of
true -> ok; true ->
false -> ok = emqx_metrics:inc('messages.dropped'), ok;
emqx_metrics:inc('messages.dropped.no_subscribers') false ->
ok = emqx_metrics:inc('messages.dropped'),
emqx_metrics:inc('messages.dropped.no_subscribers')
end. end.
-compile({inline, [subscribers/1]}). -compile({inline, [subscribers/1]}).
-spec(subscribers(emqx_types:topic() | {shard, emqx_types:topic(), non_neg_integer()}) -spec subscribers(emqx_types:topic() | {shard, emqx_types:topic(), non_neg_integer()}) ->
-> [pid()]). [pid()].
subscribers(Topic) when is_binary(Topic) -> subscribers(Topic) when is_binary(Topic) ->
lookup_value(?SUBSCRIBER, Topic, []); lookup_value(?SUBSCRIBER, Topic, []);
subscribers(Shard = {shard, _Topic, _I}) -> subscribers(Shard = {shard, _Topic, _I}) ->
lookup_value(?SUBSCRIBER, Shard, []). lookup_value(?SUBSCRIBER, Shard, []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Subscriber is down %% Subscriber is down
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(subscriber_down(pid()) -> true). -spec subscriber_down(pid()) -> true.
subscriber_down(SubPid) -> subscriber_down(SubPid) ->
lists:foreach( lists:foreach(
fun(Topic) -> fun(Topic) ->
case lookup_value(?SUBOPTION, {SubPid, Topic}) of case lookup_value(?SUBOPTION, {SubPid, Topic}) of
SubOpts when is_map(SubOpts) -> SubOpts when is_map(SubOpts) ->
_ = emqx_broker_helper:reclaim_seq(Topic), _ = emqx_broker_helper:reclaim_seq(Topic),
true = ets:delete(?SUBOPTION, {SubPid, Topic}), true = ets:delete(?SUBOPTION, {SubPid, Topic}),
case maps:get(shard, SubOpts, 0) of case maps:get(shard, SubOpts, 0) of
0 -> true = ets:delete_object(?SUBSCRIBER, {Topic, SubPid}), 0 ->
ok = cast(pick(Topic), {unsubscribed, Topic}); true = ets:delete_object(?SUBSCRIBER, {Topic, SubPid}),
I -> true = ets:delete_object(?SUBSCRIBER, {{shard, Topic, I}, SubPid}), ok = cast(pick(Topic), {unsubscribed, Topic});
ok = cast(pick({Topic, I}), {unsubscribed, Topic, I}) I ->
end; true = ets:delete_object(?SUBSCRIBER, {{shard, Topic, I}, SubPid}),
undefined -> ok ok = cast(pick({Topic, I}), {unsubscribed, Topic, I})
end end;
end, lookup_value(?SUBSCRIPTION, SubPid, [])), undefined ->
ok
end
end,
lookup_value(?SUBSCRIPTION, SubPid, [])
),
ets:delete(?SUBSCRIPTION, SubPid). ets:delete(?SUBSCRIPTION, SubPid).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Management APIs %% Management APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(subscriptions(pid() | emqx_types:subid()) -spec subscriptions(pid() | emqx_types:subid()) ->
-> [{emqx_types:topic(), emqx_types:subopts()}]). [{emqx_types:topic(), emqx_types:subopts()}].
subscriptions(SubPid) when is_pid(SubPid) -> subscriptions(SubPid) when is_pid(SubPid) ->
[{Topic, lookup_value(?SUBOPTION, {SubPid, Topic}, #{})} [
|| Topic <- lookup_value(?SUBSCRIPTION, SubPid, [])]; {Topic, lookup_value(?SUBOPTION, {SubPid, Topic}, #{})}
|| Topic <- lookup_value(?SUBSCRIPTION, SubPid, [])
];
subscriptions(SubId) -> subscriptions(SubId) ->
case emqx_broker_helper:lookup_subpid(SubId) of case emqx_broker_helper:lookup_subpid(SubId) of
SubPid when is_pid(SubPid) -> SubPid when is_pid(SubPid) ->
subscriptions(SubPid); subscriptions(SubPid);
undefined -> [] undefined ->
[]
end. end.
-spec(subscriptions_via_topic(emqx_types:topic()) -> [emqx_types:subopts()]). -spec subscriptions_via_topic(emqx_types:topic()) -> [emqx_types:subopts()].
subscriptions_via_topic(Topic) -> subscriptions_via_topic(Topic) ->
MatchSpec = [{{{'_', '$1'}, '_'}, [{'=:=', '$1', Topic}], ['$_']}], MatchSpec = [{{{'_', '$1'}, '_'}, [{'=:=', '$1', Topic}], ['$_']}],
ets:select(?SUBOPTION, MatchSpec). ets:select(?SUBOPTION, MatchSpec).
-spec(subscribed(pid() | emqx_types:subid(), emqx_types:topic()) -> boolean()). -spec subscribed(pid() | emqx_types:subid(), emqx_types:topic()) -> boolean().
subscribed(SubPid, Topic) when is_pid(SubPid) -> subscribed(SubPid, Topic) when is_pid(SubPid) ->
ets:member(?SUBOPTION, {SubPid, Topic}); ets:member(?SUBOPTION, {SubPid, Topic});
subscribed(SubId, Topic) when ?IS_SUBID(SubId) -> subscribed(SubId, Topic) when ?IS_SUBID(SubId) ->
SubPid = emqx_broker_helper:lookup_subpid(SubId), SubPid = emqx_broker_helper:lookup_subpid(SubId),
ets:member(?SUBOPTION, {SubPid, Topic}). ets:member(?SUBOPTION, {SubPid, Topic}).
-spec(get_subopts(pid(), emqx_types:topic()) -> maybe(emqx_types:subopts())). -spec get_subopts(pid(), emqx_types:topic()) -> maybe(emqx_types:subopts()).
get_subopts(SubPid, Topic) when is_pid(SubPid), is_binary(Topic) -> get_subopts(SubPid, Topic) when is_pid(SubPid), is_binary(Topic) ->
lookup_value(?SUBOPTION, {SubPid, Topic}); lookup_value(?SUBOPTION, {SubPid, Topic});
get_subopts(SubId, Topic) when ?IS_SUBID(SubId) -> get_subopts(SubId, Topic) when ?IS_SUBID(SubId) ->
case emqx_broker_helper:lookup_subpid(SubId) of case emqx_broker_helper:lookup_subpid(SubId) of
SubPid when is_pid(SubPid) -> SubPid when is_pid(SubPid) ->
get_subopts(SubPid, Topic); get_subopts(SubPid, Topic);
undefined -> undefined undefined ->
undefined
end. end.
-spec(set_subopts(emqx_types:topic(), emqx_types:subopts()) -> boolean()). -spec set_subopts(emqx_types:topic(), emqx_types:subopts()) -> boolean().
set_subopts(Topic, NewOpts) when is_binary(Topic), is_map(NewOpts) -> set_subopts(Topic, NewOpts) when is_binary(Topic), is_map(NewOpts) ->
set_subopts(self(), Topic, NewOpts). set_subopts(self(), Topic, NewOpts).
@ -392,10 +433,11 @@ set_subopts(SubPid, Topic, NewOpts) ->
case ets:lookup(?SUBOPTION, Sub) of case ets:lookup(?SUBOPTION, Sub) of
[{_, OldOpts}] -> [{_, OldOpts}] ->
ets:insert(?SUBOPTION, {Sub, maps:merge(OldOpts, NewOpts)}); ets:insert(?SUBOPTION, {Sub, maps:merge(OldOpts, NewOpts)});
[] -> false [] ->
false
end. end.
-spec(topics() -> [emqx_types:topic()]). -spec topics() -> [emqx_types:topic()].
topics() -> topics() ->
emqx_router:topics(). emqx_router:topics().
@ -441,18 +483,18 @@ init([Pool, Id]) ->
handle_call({subscribe, Topic}, _From, State) -> handle_call({subscribe, Topic}, _From, State) ->
Ok = emqx_router:do_add_route(Topic), Ok = emqx_router:do_add_route(Topic),
{reply, Ok, State}; {reply, Ok, State};
handle_call({subscribe, Topic, I}, _From, State) -> handle_call({subscribe, Topic, I}, _From, State) ->
Shard = {Topic, I}, Shard = {Topic, I},
Ok = case get(Shard) of Ok =
undefined -> case get(Shard) of
_ = put(Shard, true), undefined ->
true = ets:insert(?SUBSCRIBER, {Topic, {shard, I}}), _ = put(Shard, true),
cast(pick(Topic), {subscribe, Topic}); true = ets:insert(?SUBSCRIBER, {Topic, {shard, I}}),
true -> ok cast(pick(Topic), {subscribe, Topic});
end, true ->
ok
end,
{reply, Ok, State}; {reply, Ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -460,30 +502,28 @@ handle_call(Req, _From, State) ->
handle_cast({subscribe, Topic}, State) -> handle_cast({subscribe, Topic}, State) ->
case emqx_router:do_add_route(Topic) of case emqx_router:do_add_route(Topic) of
ok -> ok; ok -> ok;
{error, Reason} -> {error, Reason} -> ?SLOG(error, #{msg => "failed_to_add_route", reason => Reason})
?SLOG(error, #{msg => "failed_to_add_route", reason => Reason})
end, end,
{noreply, State}; {noreply, State};
handle_cast({unsubscribed, Topic}, State) -> handle_cast({unsubscribed, Topic}, State) ->
case ets:member(?SUBSCRIBER, Topic) of case ets:member(?SUBSCRIBER, Topic) of
false -> false ->
_ = emqx_router:do_delete_route(Topic), _ = emqx_router:do_delete_route(Topic),
ok; ok;
true -> ok true ->
ok
end, end,
{noreply, State}; {noreply, State};
handle_cast({unsubscribed, Topic, I}, State) -> handle_cast({unsubscribed, Topic, I}, State) ->
case ets:member(?SUBSCRIBER, {shard, Topic, I}) of case ets:member(?SUBSCRIBER, {shard, Topic, I}) of
false -> false ->
_ = erase({Topic, I}), _ = erase({Topic, I}),
true = ets:delete_object(?SUBSCRIBER, {Topic, {shard, I}}), true = ets:delete_object(?SUBSCRIBER, {Topic, {shard, I}}),
cast(pick(Topic), {unsubscribed, Topic}); cast(pick(Topic), {unsubscribed, Topic});
true -> ok true ->
ok
end, end,
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
@ -502,12 +542,15 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(do_dispatch(emqx_types:topic(), emqx_types:delivery()) -> emqx_types:deliver_result()). -spec do_dispatch(emqx_types:topic(), emqx_types:delivery()) -> emqx_types:deliver_result().
do_dispatch(Topic, #delivery{message = Msg}) -> do_dispatch(Topic, #delivery{message = Msg}) ->
DispN = lists:foldl( DispN = lists:foldl(
fun(Sub, N) -> fun(Sub, N) ->
N + do_dispatch(Sub, Topic, Msg) N + do_dispatch(Sub, Topic, Msg)
end, 0, subscribers(Topic)), end,
0,
subscribers(Topic)
),
case DispN of case DispN of
0 -> 0 ->
ok = emqx_hooks:run('message.dropped', [Msg, #{node => node()}, no_subscribers]), ok = emqx_hooks:run('message.dropped', [Msg, #{node => node()}, no_subscribers]),
@ -520,11 +563,16 @@ do_dispatch(Topic, #delivery{message = Msg}) ->
do_dispatch(SubPid, Topic, Msg) when is_pid(SubPid) -> do_dispatch(SubPid, Topic, Msg) when is_pid(SubPid) ->
case erlang:is_process_alive(SubPid) of case erlang:is_process_alive(SubPid) of
true -> true ->
SubPid ! {deliver, Topic, Msg}, 1; SubPid ! {deliver, Topic, Msg},
false -> 0 1;
false ->
0
end; end;
do_dispatch({shard, I}, Topic, Msg) -> do_dispatch({shard, I}, Topic, Msg) ->
lists:foldl( lists:foldl(
fun(SubPid, N) -> fun(SubPid, N) ->
N + do_dispatch(SubPid, Topic, Msg) N + do_dispatch(SubPid, Topic, Msg)
end, 0, subscribers({shard, Topic, I})). end,
0,
subscribers({shard, Topic, I})
).

View File

@ -25,13 +25,14 @@
run1() -> run1(80, 1000, 80, 10000). run1() -> run1(80, 1000, 80, 10000).
run1(Subs, SubOps, Pubs, PubOps) -> run1(Subs, SubOps, Pubs, PubOps) ->
run(#{subscribers => Subs, run(#{
publishers => Pubs, subscribers => Subs,
sub_ops => SubOps, publishers => Pubs,
pub_ops => PubOps, sub_ops => SubOps,
sub_ptn => <<"device/{{id}}/+/{{num}}/#">>, pub_ops => PubOps,
pub_ptn => <<"device/{{id}}/foo/{{num}}/bar/1/2/3/4/5">> sub_ptn => <<"device/{{id}}/+/{{num}}/#">>,
}). pub_ptn => <<"device/{{id}}/foo/{{num}}/bar/1/2/3/4/5">>
}).
%% setting fields: %% setting fields:
%% - subscribers: spawn this number of subscriber workers %% - subscribers: spawn this number of subscriber workers
@ -43,20 +44,23 @@ run1(Subs, SubOps, Pubs, PubOps) ->
%% replaced by worker id and {{num}} replaced by topic number. %% replaced by worker id and {{num}} replaced by topic number.
%% - pub_ptn: topic pattern used to benchmark publish (match) performance %% - pub_ptn: topic pattern used to benchmark publish (match) performance
%% e.g. a/x/{{id}}/{{num}}/foo/bar %% e.g. a/x/{{id}}/{{num}}/foo/bar
run(#{subscribers := Subs, run(
publishers := Pubs, #{
sub_ops := SubOps, subscribers := Subs,
pub_ops := PubOps publishers := Pubs,
} = Settings) -> sub_ops := SubOps,
SubsPids = start_callers(Subs, fun start_subscriber/1, Settings), pub_ops := PubOps
} = Settings
) ->
SubsPids = start_callers(Subs, fun start_subscriber/1, Settings),
PubsPids = start_callers(Pubs, fun start_publisher/1, Settings), PubsPids = start_callers(Pubs, fun start_publisher/1, Settings),
_ = collect_results(SubsPids, subscriber_ready), _ = collect_results(SubsPids, subscriber_ready),
io:format(user, "subscribe ...~n", []), io:format(user, "subscribe ...~n", []),
{T1, SubsTime} = {T1, SubsTime} =
?T(begin ?T(begin
lists:foreach(fun(Pid) -> Pid ! start_subscribe end, SubsPids), lists:foreach(fun(Pid) -> Pid ! start_subscribe end, SubsPids),
collect_results(SubsPids, subscribe_time) collect_results(SubsPids, subscribe_time)
end), end),
io:format(user, "InsertTotalTime: ~ts~n", [ns(T1)]), io:format(user, "InsertTotalTime: ~ts~n", [ns(T1)]),
io:format(user, "InsertTimeAverage: ~ts~n", [ns(SubsTime / Subs)]), io:format(user, "InsertTimeAverage: ~ts~n", [ns(SubsTime / Subs)]),
io:format(user, "InsertRps: ~p~n", [rps(Subs * SubOps, T1)]), io:format(user, "InsertRps: ~p~n", [rps(Subs * SubOps, T1)]),
@ -64,9 +68,9 @@ run(#{subscribers := Subs,
io:format(user, "lookup ...~n", []), io:format(user, "lookup ...~n", []),
{T2, PubsTime} = {T2, PubsTime} =
?T(begin ?T(begin
lists:foreach(fun(Pid) -> Pid ! start_lookup end, PubsPids), lists:foreach(fun(Pid) -> Pid ! start_lookup end, PubsPids),
collect_results(PubsPids, lookup_time) collect_results(PubsPids, lookup_time)
end), end),
io:format(user, "LookupTotalTime: ~ts~n", [ns(T2)]), io:format(user, "LookupTotalTime: ~ts~n", [ns(T2)]),
io:format(user, "LookupTimeAverage: ~ts~n", [ns(PubsTime / Pubs)]), io:format(user, "LookupTimeAverage: ~ts~n", [ns(PubsTime / Pubs)]),
io:format(user, "LookupRps: ~p~n", [rps(Pubs * PubOps, T2)]), io:format(user, "LookupRps: ~p~n", [rps(Pubs * PubOps, T2)]),
@ -76,14 +80,15 @@ run(#{subscribers := Subs,
io:format(user, "unsubscribe ...~n", []), io:format(user, "unsubscribe ...~n", []),
{T3, ok} = {T3, ok} =
?T(begin ?T(begin
lists:foreach(fun(Pid) -> Pid ! stop end, SubsPids), lists:foreach(fun(Pid) -> Pid ! stop end, SubsPids),
wait_until_empty() wait_until_empty()
end), end),
io:format(user, "TimeToUnsubscribeAll: ~ts~n", [ns(T3)]). io:format(user, "TimeToUnsubscribeAll: ~ts~n", [ns(T3)]).
wait_until_empty() -> wait_until_empty() ->
case emqx_trie:empty() of case emqx_trie:empty() of
true -> ok; true ->
ok;
false -> false ->
timer:sleep(5), timer:sleep(5),
wait_until_empty() wait_until_empty()
@ -98,13 +103,13 @@ ns(T) -> io_lib:format("~p(ns)", [T]).
ram_bytes() -> ram_bytes() ->
Wordsize = erlang:system_info(wordsize), Wordsize = erlang:system_info(wordsize),
mnesia:table_info(emqx_trie, memory) * Wordsize + mnesia:table_info(emqx_trie, memory) * Wordsize +
case lists:member(emqx_trie_node, ets:all()) of case lists:member(emqx_trie_node, ets:all()) of
true -> true ->
%% before 4.3 %% before 4.3
mnesia:table_info(emqx_trie_node, memory) * Wordsize; mnesia:table_info(emqx_trie_node, memory) * Wordsize;
false -> false ->
0 0
end. end.
start_callers(N, F, Settings) -> start_callers(N, F, Settings) ->
start_callers(N, F, Settings, []). start_callers(N, F, Settings, []).
@ -117,7 +122,8 @@ start_callers(N, F, Settings, Acc) ->
collect_results(Pids, Tag) -> collect_results(Pids, Tag) ->
collect_results(Pids, Tag, 0). collect_results(Pids, Tag, 0).
collect_results([], _Tag, R) -> R; collect_results([], _Tag, R) ->
R;
collect_results([Pid | Pids], Tag, R) -> collect_results([Pid | Pids], Tag, R) ->
receive receive
{Pid, Tag, N} -> {Pid, Tag, N} ->
@ -128,40 +134,43 @@ start_subscriber(#{id := Id, sub_ops := N, sub_ptn := SubPtn}) ->
Parent = self(), Parent = self(),
proc_lib:spawn_link( proc_lib:spawn_link(
fun() -> fun() ->
SubTopics = make_topics(SubPtn, Id, N), SubTopics = make_topics(SubPtn, Id, N),
Parent ! {self(), subscriber_ready, 0}, Parent ! {self(), subscriber_ready, 0},
receive receive
start_subscribe -> start_subscribe ->
ok ok
end, end,
{Ts, _} = ?T(subscribe(SubTopics)), {Ts, _} = ?T(subscribe(SubTopics)),
_ = erlang:send(Parent, {self(), subscribe_time, Ts/ N}), _ = erlang:send(Parent, {self(), subscribe_time, Ts / N}),
%% subscribers should not exit before publish test is done %% subscribers should not exit before publish test is done
receive receive
stop -> stop ->
ok ok
end end
end). end
).
start_publisher(#{id := Id, pub_ops := N, pub_ptn := PubPtn, subscribers := Subs}) -> start_publisher(#{id := Id, pub_ops := N, pub_ptn := PubPtn, subscribers := Subs}) ->
Parent = self(), Parent = self(),
proc_lib:spawn_link( proc_lib:spawn_link(
fun() -> fun() ->
L = lists:seq(1, N), L = lists:seq(1, N),
[Topic] = make_topics(PubPtn, (Id rem Subs) + 1, 1), [Topic] = make_topics(PubPtn, (Id rem Subs) + 1, 1),
receive receive
start_lookup -> start_lookup ->
ok ok
end, end,
{Tm, ok} = ?T(lists:foreach(fun(_) -> match(Topic) end, L)), {Tm, ok} = ?T(lists:foreach(fun(_) -> match(Topic) end, L)),
_ = erlang:send(Parent, {self(), lookup_time, Tm / N}), _ = erlang:send(Parent, {self(), lookup_time, Tm / N}),
ok ok
end). end
).
match(Topic) -> match(Topic) ->
[_] = emqx_router:match_routes(Topic). [_] = emqx_router:match_routes(Topic).
subscribe([]) -> ok; subscribe([]) ->
ok;
subscribe([Topic | Rest]) -> subscribe([Topic | Rest]) ->
ok = emqx_broker:subscribe(Topic), ok = emqx_broker:subscribe(Topic),
subscribe(Rest). subscribe(Rest).

View File

@ -21,26 +21,27 @@
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
-export([start_link/0]). -export([start_link/0]).
%% APIs %% APIs
-export([ register_sub/2 -export([
, lookup_subid/1 register_sub/2,
, lookup_subpid/1 lookup_subid/1,
, get_sub_shard/2 lookup_subpid/1,
, create_seq/1 get_sub_shard/2,
, reclaim_seq/1 create_seq/1,
]). reclaim_seq/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
@ -55,11 +56,11 @@
-define(BATCH_SIZE, 100000). -define(BATCH_SIZE, 100000).
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?HELPER}, ?MODULE, [], []). gen_server:start_link({local, ?HELPER}, ?MODULE, [], []).
-spec(register_sub(pid(), emqx_types:subid()) -> ok). -spec register_sub(pid(), emqx_types:subid()) -> ok.
register_sub(SubPid, SubId) when is_pid(SubPid) -> register_sub(SubPid, SubId) when is_pid(SubPid) ->
case ets:lookup(?SUBMON, SubPid) of case ets:lookup(?SUBMON, SubPid) of
[] -> [] ->
@ -70,31 +71,31 @@ register_sub(SubPid, SubId) when is_pid(SubPid) ->
error(subid_conflict) error(subid_conflict)
end. end.
-spec(lookup_subid(pid()) -> maybe(emqx_types:subid())). -spec lookup_subid(pid()) -> maybe(emqx_types:subid()).
lookup_subid(SubPid) when is_pid(SubPid) -> lookup_subid(SubPid) when is_pid(SubPid) ->
emqx_tables:lookup_value(?SUBMON, SubPid). emqx_tables:lookup_value(?SUBMON, SubPid).
-spec(lookup_subpid(emqx_types:subid()) -> maybe(pid())). -spec lookup_subpid(emqx_types:subid()) -> maybe(pid()).
lookup_subpid(SubId) -> lookup_subpid(SubId) ->
emqx_tables:lookup_value(?SUBID, SubId). emqx_tables:lookup_value(?SUBID, SubId).
-spec(get_sub_shard(pid(), emqx_types:topic()) -> non_neg_integer()). -spec get_sub_shard(pid(), emqx_types:topic()) -> non_neg_integer().
get_sub_shard(SubPid, Topic) -> get_sub_shard(SubPid, Topic) ->
case create_seq(Topic) of case create_seq(Topic) of
Seq when Seq =< ?SHARD -> 0; Seq when Seq =< ?SHARD -> 0;
_ -> erlang:phash2(SubPid, shards_num()) + 1 _ -> erlang:phash2(SubPid, shards_num()) + 1
end. end.
-spec(shards_num() -> pos_integer()). -spec shards_num() -> pos_integer().
shards_num() -> shards_num() ->
%% Dynamic sharding later... %% Dynamic sharding later...
ets:lookup_element(?HELPER, shards, 2). ets:lookup_element(?HELPER, shards, 2).
-spec(create_seq(emqx_types:topic()) -> emqx_sequence:seqid()). -spec create_seq(emqx_types:topic()) -> emqx_sequence:seqid().
create_seq(Topic) -> create_seq(Topic) ->
emqx_sequence:nextval(?SUBSEQ, Topic). emqx_sequence:nextval(?SUBSEQ, Topic).
-spec(reclaim_seq(emqx_types:topic()) -> emqx_sequence:seqid()). -spec reclaim_seq(emqx_types:topic()) -> emqx_sequence:seqid().
reclaim_seq(Topic) -> reclaim_seq(Topic) ->
emqx_sequence:reclaim(?SUBSEQ, Topic). emqx_sequence:reclaim(?SUBSEQ, Topic).
@ -125,7 +126,6 @@ handle_cast({register_sub, SubPid, SubId}, State = #{pmon := PMon}) ->
true = (SubId =:= undefined) orelse ets:insert(?SUBID, {SubId, SubPid}), true = (SubId =:= undefined) orelse ets:insert(?SUBID, {SubId, SubPid}),
true = ets:insert(?SUBMON, {SubPid, SubId}), true = ets:insert(?SUBMON, {SubPid, SubId}),
{noreply, State#{pmon := emqx_pmon:monitor(SubPid, PMon)}}; {noreply, State#{pmon := emqx_pmon:monitor(SubPid, PMon)}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
@ -133,10 +133,10 @@ handle_cast(Msg, State) ->
handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #{pmon := PMon}) -> handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #{pmon := PMon}) ->
SubPids = [SubPid | emqx_misc:drain_down(?BATCH_SIZE)], SubPids = [SubPid | emqx_misc:drain_down(?BATCH_SIZE)],
ok = emqx_pool:async_submit( ok = emqx_pool:async_submit(
fun lists:foreach/2, [fun clean_down/1, SubPids]), fun lists:foreach/2, [fun clean_down/1, SubPids]
),
{_, PMon1} = emqx_pmon:erase_all(SubPids, PMon), {_, PMon1} = emqx_pmon:erase_all(SubPids, PMon),
{noreply, State#{pmon := PMon1}}; {noreply, State#{pmon := PMon1}};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -156,9 +156,10 @@ clean_down(SubPid) ->
case ets:lookup(?SUBMON, SubPid) of case ets:lookup(?SUBMON, SubPid) of
[{_, SubId}] -> [{_, SubId}] ->
true = ets:delete(?SUBMON, SubPid), true = ets:delete(?SUBMON, SubPid),
true = (SubId =:= undefined) true =
orelse ets:delete_object(?SUBID, {SubId, SubPid}), (SubId =:= undefined) orelse
ets:delete_object(?SUBID, {SubId, SubPid}),
emqx_broker:subscriber_down(SubPid); emqx_broker:subscriber_down(SubPid);
[] -> ok [] ->
ok
end. end.

View File

@ -32,32 +32,41 @@ start_link() ->
init([]) -> init([]) ->
%% Broker pool %% Broker pool
PoolSize = emqx_vm:schedulers() * 2, PoolSize = emqx_vm:schedulers() * 2,
BrokerPool = emqx_pool_sup:spec([broker_pool, hash, PoolSize, BrokerPool = emqx_pool_sup:spec([
{emqx_broker, start_link, []}]), broker_pool,
hash,
PoolSize,
{emqx_broker, start_link, []}
]),
%% Shared subscription %% Shared subscription
SharedSub = #{id => shared_sub, SharedSub = #{
start => {emqx_shared_sub, start_link, []}, id => shared_sub,
restart => permanent, start => {emqx_shared_sub, start_link, []},
shutdown => 2000, restart => permanent,
type => worker, shutdown => 2000,
modules => [emqx_shared_sub]}, type => worker,
modules => [emqx_shared_sub]
},
%% Authentication %% Authentication
AuthNSup = #{id => emqx_authentication_sup, AuthNSup = #{
start => {emqx_authentication_sup, start_link, []}, id => emqx_authentication_sup,
restart => permanent, start => {emqx_authentication_sup, start_link, []},
shutdown => infinity, restart => permanent,
type => supervisor, shutdown => infinity,
modules => [emqx_authentication_sup]}, type => supervisor,
modules => [emqx_authentication_sup]
},
%% Broker helper %% Broker helper
Helper = #{id => helper, Helper = #{
start => {emqx_broker_helper, start_link, []}, id => helper,
restart => permanent, start => {emqx_broker_helper, start_link, []},
shutdown => 2000, restart => permanent,
type => worker, shutdown => 2000,
modules => [emqx_broker_helper]}, type => worker,
modules => [emqx_broker_helper]
},
{ok, {{one_for_all, 0, 1}, [BrokerPool, SharedSub, AuthNSup, Helper]}}. {ok, {{one_for_all, 0, 1}, [BrokerPool, SharedSub, AuthNSup, Helper]}}.

File diff suppressed because it is too large Load Diff

View File

@ -23,80 +23,89 @@
-include("types.hrl"). -include("types.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
-export([start_link/0]). -export([start_link/0]).
-export([ register_channel/3 -export([
, unregister_channel/1 register_channel/3,
, insert_channel_info/3 unregister_channel/1,
]). insert_channel_info/3
]).
-export([connection_closed/1]). -export([connection_closed/1]).
-export([ get_chan_info/1 -export([
, get_chan_info/2 get_chan_info/1,
, set_chan_info/2 get_chan_info/2,
]). set_chan_info/2
]).
-export([ get_chan_stats/1 -export([
, get_chan_stats/2 get_chan_stats/1,
, set_chan_stats/2 get_chan_stats/2,
]). set_chan_stats/2
]).
-export([get_chann_conn_mod/2]). -export([get_chann_conn_mod/2]).
-export([ open_session/3 -export([
, discard_session/1 open_session/3,
, discard_session/2 discard_session/1,
, takeover_session/1 discard_session/2,
, takeover_session/2 takeover_session/1,
, kick_session/1 takeover_session/2,
, kick_session/2 kick_session/1,
]). kick_session/2
]).
-export([ lookup_channels/1 -export([
, lookup_channels/2 lookup_channels/1,
lookup_channels/2,
, lookup_client/1 lookup_client/1
]). ]).
%% Test/debug interface %% Test/debug interface
-export([ all_channels/0 -export([
, all_client_ids/0 all_channels/0,
]). all_client_ids/0
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
%% Internal export %% Internal export
-export([ stats_fun/0 -export([
, clean_down/1 stats_fun/0,
, mark_channel_connected/1 clean_down/1,
, mark_channel_disconnected/1 mark_channel_connected/1,
, get_connected_client_count/0 mark_channel_disconnected/1,
get_connected_client_count/0,
, do_kick_session/3 do_kick_session/3,
, do_get_chan_stats/2 do_get_chan_stats/2,
, do_get_chan_info/2 do_get_chan_info/2,
, do_get_chann_conn_mod/2 do_get_chann_conn_mod/2
]). ]).
-export_type([ channel_info/0 -export_type([
, chan_pid/0 channel_info/0,
]). chan_pid/0
]).
-type(chan_pid() :: pid()). -type chan_pid() :: pid().
-type(channel_info() :: { _Chan :: {emqx_types:clientid(), pid()} -type channel_info() :: {
, _Info :: emqx_types:infos() _Chan :: {emqx_types:clientid(), pid()},
, _Stats :: emqx_types:stats() _Info :: emqx_types:infos(),
}). _Stats :: emqx_types:stats()
}.
-include("emqx_cm.hrl"). -include("emqx_cm.hrl").
@ -106,12 +115,12 @@
-define(CHAN_INFO_TAB, emqx_channel_info). -define(CHAN_INFO_TAB, emqx_channel_info).
-define(CHAN_LIVE_TAB, emqx_channel_live). -define(CHAN_LIVE_TAB, emqx_channel_live).
-define(CHAN_STATS, -define(CHAN_STATS, [
[{?CHAN_TAB, 'channels.count', 'channels.max'}, {?CHAN_TAB, 'channels.count', 'channels.max'},
{?CHAN_TAB, 'sessions.count', 'sessions.max'}, {?CHAN_TAB, 'sessions.count', 'sessions.max'},
{?CHAN_CONN_TAB, 'connections.count', 'connections.max'}, {?CHAN_CONN_TAB, 'connections.count', 'connections.max'},
{?CHAN_LIVE_TAB, 'live_connections.count', 'live_connections.max'} {?CHAN_LIVE_TAB, 'live_connections.count', 'live_connections.max'}
]). ]).
%% Batch drain %% Batch drain
-define(BATCH_SIZE, 100000). -define(BATCH_SIZE, 100000).
@ -120,12 +129,13 @@
-define(CM, ?MODULE). -define(CM, ?MODULE).
%% linting overrides %% linting overrides
-elvis([ {elvis_style, invalid_dynamic_call, #{ignore => [emqx_cm]}} -elvis([
, {elvis_style, god_modules, #{ignore => [emqx_cm]}} {elvis_style, invalid_dynamic_call, #{ignore => [emqx_cm]}},
]). {elvis_style, god_modules, #{ignore => [emqx_cm]}}
]).
%% @doc Start the channel manager. %% @doc Start the channel manager.
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?CM}, ?MODULE, [], []). gen_server:start_link({local, ?CM}, ?MODULE, [], []).
@ -134,9 +144,11 @@ start_link() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Insert/Update the channel info and stats to emqx_channel table %% @doc Insert/Update the channel info and stats to emqx_channel table
-spec(insert_channel_info(emqx_types:clientid(), -spec insert_channel_info(
emqx_types:infos(), emqx_types:clientid(),
emqx_types:stats()) -> ok). emqx_types:infos(),
emqx_types:stats()
) -> ok.
insert_channel_info(ClientId, Info, Stats) -> insert_channel_info(ClientId, Info, Stats) ->
Chan = {ClientId, self()}, Chan = {ClientId, self()},
true = ets:insert(?CHAN_INFO_TAB, {Chan, Info, Stats}), true = ets:insert(?CHAN_INFO_TAB, {Chan, Info, Stats}),
@ -160,7 +172,7 @@ register_channel(ClientId, ChanPid, #{conn_mod := ConnMod}) when is_pid(ChanPid)
cast({registered, Chan}). cast({registered, Chan}).
%% @doc Unregister a channel. %% @doc Unregister a channel.
-spec(unregister_channel(emqx_types:clientid()) -> ok). -spec unregister_channel(emqx_types:clientid()) -> ok.
unregister_channel(ClientId) when is_binary(ClientId) -> unregister_channel(ClientId) when is_binary(ClientId) ->
true = do_unregister_channel({ClientId, self()}), true = do_unregister_channel({ClientId, self()}),
ok. ok.
@ -172,143 +184,158 @@ do_unregister_channel(Chan) ->
true = ets:delete(?CHAN_INFO_TAB, Chan), true = ets:delete(?CHAN_INFO_TAB, Chan),
ets:delete_object(?CHAN_TAB, Chan). ets:delete_object(?CHAN_TAB, Chan).
-spec(connection_closed(emqx_types:clientid()) -> true). -spec connection_closed(emqx_types:clientid()) -> true.
connection_closed(ClientId) -> connection_closed(ClientId) ->
connection_closed(ClientId, self()). connection_closed(ClientId, self()).
-spec(connection_closed(emqx_types:clientid(), chan_pid()) -> true). -spec connection_closed(emqx_types:clientid(), chan_pid()) -> true.
connection_closed(ClientId, ChanPid) -> connection_closed(ClientId, ChanPid) ->
ets:delete_object(?CHAN_CONN_TAB, {ClientId, ChanPid}). ets:delete_object(?CHAN_CONN_TAB, {ClientId, ChanPid}).
%% @doc Get info of a channel. %% @doc Get info of a channel.
-spec(get_chan_info(emqx_types:clientid()) -> maybe(emqx_types:infos())). -spec get_chan_info(emqx_types:clientid()) -> maybe(emqx_types:infos()).
get_chan_info(ClientId) -> get_chan_info(ClientId) ->
with_channel(ClientId, fun(ChanPid) -> get_chan_info(ClientId, ChanPid) end). with_channel(ClientId, fun(ChanPid) -> get_chan_info(ClientId, ChanPid) end).
-spec(do_get_chan_info(emqx_types:clientid(), chan_pid()) -spec do_get_chan_info(emqx_types:clientid(), chan_pid()) ->
-> maybe(emqx_types:infos())). maybe(emqx_types:infos()).
do_get_chan_info(ClientId, ChanPid) -> do_get_chan_info(ClientId, ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try ets:lookup_element(?CHAN_INFO_TAB, Chan, 2) try
ets:lookup_element(?CHAN_INFO_TAB, Chan, 2)
catch catch
error:badarg -> undefined error:badarg -> undefined
end. end.
-spec(get_chan_info(emqx_types:clientid(), chan_pid()) -spec get_chan_info(emqx_types:clientid(), chan_pid()) ->
-> maybe(emqx_types:infos())). maybe(emqx_types:infos()).
get_chan_info(ClientId, ChanPid) -> get_chan_info(ClientId, ChanPid) ->
wrap_rpc(emqx_cm_proto_v1:get_chan_info(ClientId, ChanPid)). wrap_rpc(emqx_cm_proto_v1:get_chan_info(ClientId, ChanPid)).
%% @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().
set_chan_info(ClientId, Info) when is_binary(ClientId) -> set_chan_info(ClientId, Info) when is_binary(ClientId) ->
Chan = {ClientId, self()}, Chan = {ClientId, self()},
try ets:update_element(?CHAN_INFO_TAB, Chan, {2, Info}) try
ets:update_element(?CHAN_INFO_TAB, Chan, {2, Info})
catch catch
error:badarg -> false error:badarg -> false
end. end.
%% @doc Get channel's stats. %% @doc Get channel's stats.
-spec(get_chan_stats(emqx_types:clientid()) -> maybe(emqx_types:stats())). -spec get_chan_stats(emqx_types:clientid()) -> maybe(emqx_types:stats()).
get_chan_stats(ClientId) -> get_chan_stats(ClientId) ->
with_channel(ClientId, fun(ChanPid) -> get_chan_stats(ClientId, ChanPid) end). with_channel(ClientId, fun(ChanPid) -> get_chan_stats(ClientId, ChanPid) end).
-spec(do_get_chan_stats(emqx_types:clientid(), chan_pid()) -spec do_get_chan_stats(emqx_types:clientid(), chan_pid()) ->
-> maybe(emqx_types:stats())). maybe(emqx_types:stats()).
do_get_chan_stats(ClientId, ChanPid) -> do_get_chan_stats(ClientId, ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try ets:lookup_element(?CHAN_INFO_TAB, Chan, 3) try
ets:lookup_element(?CHAN_INFO_TAB, Chan, 3)
catch catch
error:badarg -> undefined error:badarg -> undefined
end. end.
-spec(get_chan_stats(emqx_types:clientid(), chan_pid()) -spec get_chan_stats(emqx_types:clientid(), chan_pid()) ->
-> maybe(emqx_types:stats())). maybe(emqx_types:stats()).
get_chan_stats(ClientId, ChanPid) -> get_chan_stats(ClientId, ChanPid) ->
wrap_rpc(emqx_cm_proto_v1:get_chan_stats(ClientId, ChanPid)). wrap_rpc(emqx_cm_proto_v1:get_chan_stats(ClientId, ChanPid)).
%% @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().
set_chan_stats(ClientId, Stats) when is_binary(ClientId) -> set_chan_stats(ClientId, Stats) when is_binary(ClientId) ->
set_chan_stats(ClientId, self(), Stats). set_chan_stats(ClientId, self(), Stats).
-spec(set_chan_stats(emqx_types:clientid(), chan_pid(), emqx_types:stats()) -spec set_chan_stats(emqx_types:clientid(), chan_pid(), emqx_types:stats()) ->
-> boolean()). boolean().
set_chan_stats(ClientId, ChanPid, Stats) -> set_chan_stats(ClientId, ChanPid, Stats) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try ets:update_element(?CHAN_INFO_TAB, Chan, {3, Stats}) try
ets:update_element(?CHAN_INFO_TAB, Chan, {3, Stats})
catch catch
error:badarg -> false error:badarg -> false
end. end.
%% @doc Open a session. %% @doc Open a session.
-spec(open_session(boolean(), emqx_types:clientinfo(), emqx_types:conninfo()) -spec open_session(boolean(), emqx_types:clientinfo(), emqx_types:conninfo()) ->
-> {ok, #{session := emqx_session:session(), {ok, #{
present := boolean(), session := emqx_session:session(),
pendings => list()}} present := boolean(),
| {error, Reason :: term()}). pendings => list()
}}
| {error, Reason :: term()}.
open_session(true, ClientInfo = #{clientid := ClientId}, ConnInfo) -> open_session(true, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
Self = self(), Self = self(),
CleanStart = fun(_) -> CleanStart = fun(_) ->
ok = discard_session(ClientId), ok = discard_session(ClientId),
ok = emqx_persistent_session:discard_if_present(ClientId), ok = emqx_persistent_session:discard_if_present(ClientId),
Session = create_session(ClientInfo, ConnInfo), Session = create_session(ClientInfo, ConnInfo),
Session1 = emqx_persistent_session:persist(ClientInfo, ConnInfo, Session), Session1 = emqx_persistent_session:persist(ClientInfo, ConnInfo, Session),
register_channel(ClientId, Self, ConnInfo), register_channel(ClientId, Self, ConnInfo),
{ok, #{session => Session1, present => false}} {ok, #{session => Session1, present => false}}
end, end,
emqx_cm_locker:trans(ClientId, CleanStart); emqx_cm_locker:trans(ClientId, CleanStart);
open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) -> open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
Self = self(), Self = self(),
ResumeStart = fun(_) -> ResumeStart = fun(_) ->
CreateSess = CreateSess =
fun() -> fun() ->
Session = create_session(ClientInfo, ConnInfo), Session = create_session(ClientInfo, ConnInfo),
Session1 = emqx_persistent_session:persist( Session1 = emqx_persistent_session:persist(
ClientInfo,ConnInfo, Session), ClientInfo, ConnInfo, Session
register_channel(ClientId, Self, ConnInfo), ),
{ok, #{session => Session1, present => false}} register_channel(ClientId, Self, ConnInfo),
end, {ok, #{session => Session1, present => false}}
case takeover_session(ClientId) of end,
{persistent, Session} -> case takeover_session(ClientId) of
%% This is a persistent session without a managing process. {persistent, Session} ->
{Session1, Pendings} = %% This is a persistent session without a managing process.
emqx_persistent_session:resume(ClientInfo, ConnInfo, Session), {Session1, Pendings} =
register_channel(ClientId, Self, ConnInfo), emqx_persistent_session:resume(ClientInfo, ConnInfo, Session),
register_channel(ClientId, Self, ConnInfo),
{ok, #{session => Session1, {ok, #{
present => true, session => Session1,
pendings => Pendings}}; present => true,
{living, ConnMod, ChanPid, Session} -> pendings => Pendings
ok = emqx_session:resume(ClientInfo, Session), }};
case request_stepdown( {living, ConnMod, ChanPid, Session} ->
{takeover, 'end'}, ok = emqx_session:resume(ClientInfo, Session),
ConnMod, case
ChanPid) of request_stepdown(
{ok, Pendings} -> {takeover, 'end'},
Session1 = emqx_persistent_session:persist( ConnMod,
ClientInfo, ConnInfo, Session), ChanPid
register_channel(ClientId, Self, ConnInfo), )
{ok, #{session => Session1, of
present => true, {ok, Pendings} ->
pendings => Pendings}}; Session1 = emqx_persistent_session:persist(
{error, _} -> ClientInfo, ConnInfo, Session
CreateSess() ),
end; register_channel(ClientId, Self, ConnInfo),
{expired, OldSession} -> {ok, #{
_ = emqx_persistent_session:discard(ClientId, OldSession), session => Session1,
Session = create_session(ClientInfo, ConnInfo), present => true,
Session1 = emqx_persistent_session:persist( ClientInfo pendings => Pendings
, ConnInfo }};
, Session {error, _} ->
), CreateSess()
register_channel(ClientId, Self, ConnInfo), end;
{ok, #{session => Session1, present => false}}; {expired, OldSession} ->
none -> _ = emqx_persistent_session:discard(ClientId, OldSession),
CreateSess() Session = create_session(ClientInfo, ConnInfo),
end Session1 = emqx_persistent_session:persist(
end, ClientInfo,
ConnInfo,
Session
),
register_channel(ClientId, Self, ConnInfo),
{ok, #{session => Session1, present => false}};
none ->
CreateSess()
end
end,
emqx_cm_locker:trans(ClientId, ResumeStart). emqx_cm_locker:trans(ClientId, ResumeStart).
create_session(ClientInfo, ConnInfo) -> create_session(ClientInfo, ConnInfo) ->
@ -318,36 +345,40 @@ create_session(ClientInfo, ConnInfo) ->
ok = emqx_hooks:run('session.created', [ClientInfo, emqx_session:info(Session)]), ok = emqx_hooks:run('session.created', [ClientInfo, emqx_session:info(Session)]),
Session. Session.
get_session_confs(#{zone := Zone, clientid := ClientId}, #{receive_maximum := MaxInflight, expiry_interval := EI}) -> get_session_confs(#{zone := Zone, clientid := ClientId}, #{
#{clientid => ClientId, receive_maximum := MaxInflight, expiry_interval := EI
max_subscriptions => get_mqtt_conf(Zone, max_subscriptions), }) ->
upgrade_qos => get_mqtt_conf(Zone, upgrade_qos), #{
max_inflight => MaxInflight, clientid => ClientId,
retry_interval => get_mqtt_conf(Zone, retry_interval), max_subscriptions => get_mqtt_conf(Zone, max_subscriptions),
await_rel_timeout => get_mqtt_conf(Zone, await_rel_timeout), upgrade_qos => get_mqtt_conf(Zone, upgrade_qos),
mqueue => mqueue_confs(Zone), max_inflight => MaxInflight,
%% TODO: Add conf for allowing/disallowing persistent sessions. retry_interval => get_mqtt_conf(Zone, retry_interval),
%% Note that the connection info is already enriched to have await_rel_timeout => get_mqtt_conf(Zone, await_rel_timeout),
%% default config values for session expiry. mqueue => mqueue_confs(Zone),
is_persistent => EI > 0 %% TODO: Add conf for allowing/disallowing persistent sessions.
}. %% Note that the connection info is already enriched to have
%% default config values for session expiry.
is_persistent => EI > 0
}.
mqueue_confs(Zone) -> mqueue_confs(Zone) ->
#{max_len => get_mqtt_conf(Zone, max_mqueue_len), #{
store_qos0 => get_mqtt_conf(Zone, mqueue_store_qos0), max_len => get_mqtt_conf(Zone, max_mqueue_len),
priorities => get_mqtt_conf(Zone, mqueue_priorities), store_qos0 => get_mqtt_conf(Zone, mqueue_store_qos0),
default_priority => get_mqtt_conf(Zone, mqueue_default_priority) priorities => get_mqtt_conf(Zone, mqueue_priorities),
}. default_priority => get_mqtt_conf(Zone, mqueue_default_priority)
}.
get_mqtt_conf(Zone, Key) -> get_mqtt_conf(Zone, Key) ->
emqx_config:get_zone_conf(Zone, [mqtt, Key]). emqx_config:get_zone_conf(Zone, [mqtt, Key]).
%% @doc Try to takeover a session. %% @doc Try to takeover a session.
-spec takeover_session(emqx_types:clientid()) -> -spec takeover_session(emqx_types:clientid()) ->
none none
| {living, atom(), pid(), emqx_session:session()} | {living, atom(), pid(), emqx_session:session()}
| {persistent, emqx_session:session()} | {persistent, emqx_session:session()}
| {expired, emqx_session:session()}. | {expired, emqx_session:session()}.
takeover_session(ClientId) -> takeover_session(ClientId) ->
case lookup_channels(ClientId) of case lookup_channels(ClientId) of
[] -> [] ->
@ -357,20 +388,28 @@ takeover_session(ClientId) ->
ChanPids -> ChanPids ->
[ChanPid | StalePids] = lists:reverse(ChanPids), [ChanPid | StalePids] = lists:reverse(ChanPids),
?SLOG(warning, #{msg => "more_than_one_channel_found", chan_pids => ChanPids}), ?SLOG(warning, #{msg => "more_than_one_channel_found", chan_pids => ChanPids}),
lists:foreach(fun(StalePid) -> lists:foreach(
catch discard_session(ClientId, StalePid) fun(StalePid) ->
end, StalePids), catch discard_session(ClientId, StalePid)
end,
StalePids
),
takeover_session(ClientId, ChanPid) takeover_session(ClientId, ChanPid)
end. end.
takeover_session(ClientId, Pid) -> takeover_session(ClientId, Pid) ->
try do_takeover_session(ClientId, Pid) try
do_takeover_session(ClientId, Pid)
catch catch
_ : R when R == noproc; _:R when
R == timeout; R == noproc;
R == unexpected_exception -> %% request_stepdown/3 R == timeout;
%% request_stepdown/3
R == unexpected_exception
->
emqx_persistent_session:lookup(ClientId); emqx_persistent_session:lookup(ClientId);
_ : {'EXIT', {noproc, _}} -> % rpc_call/3 % rpc_call/3
_:{'EXIT', {noproc, _}} ->
emqx_persistent_session:lookup(ClientId) emqx_persistent_session:lookup(ClientId)
end. end.
@ -390,7 +429,7 @@ do_takeover_session(ClientId, ChanPid) ->
wrap_rpc(emqx_cm_proto_v1:takeover_session(ClientId, ChanPid)). wrap_rpc(emqx_cm_proto_v1:takeover_session(ClientId, ChanPid)).
%% @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;
@ -401,11 +440,12 @@ discard_session(ClientId) when is_binary(ClientId) ->
%% If failed to kick (e.g. timeout) force a kill. %% If failed to kick (e.g. timeout) force a kill.
%% Keeping the stale pid around, or returning error or raise an exception %% Keeping the stale pid around, or returning error or raise an exception
%% benefits nobody. %% benefits nobody.
-spec request_stepdown(Action, module(), pid()) -spec request_stepdown(Action, module(), pid()) ->
-> ok ok
| {ok, emqx_session:session() | list(emqx_type:deliver())} | {ok, emqx_session:session() | list(emqx_type:deliver())}
| {error, term()} | {error, term()}
when Action :: kick | discard | {takeover, 'begin'} | {takeover, 'end'}. when
Action :: kick | discard | {takeover, 'begin'} | {takeover, 'end'}.
request_stepdown(Action, ConnMod, Pid) -> request_stepdown(Action, ConnMod, Pid) ->
Timeout = Timeout =
case Action == kick orelse Action == discard of case Action == kick orelse Action == discard of
@ -420,27 +460,40 @@ request_stepdown(Action, ConnMod, Pid) ->
ok -> ok; ok -> ok;
Reply -> {ok, Reply} Reply -> {ok, Reply}
catch catch
_ : noproc -> % emqx_ws_connection: call % emqx_ws_connection: call
_:noproc ->
ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}),
{error, noproc}; {error, noproc};
_ : {noproc, _} -> % emqx_connection: gen_server:call % emqx_connection: gen_server:call
_:{noproc, _} ->
ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}),
{error, noproc}; {error, noproc};
_ : {shutdown, _} -> _:{shutdown, _} ->
ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}),
{error, noproc}; {error, noproc};
_ : {{shutdown, _}, _} -> _:{{shutdown, _}, _} ->
ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}),
{error, noproc}; {error, noproc};
_ : {timeout, {gen_server, call, _}} -> _:{timeout, {gen_server, call, _}} ->
?tp(warning, "session_stepdown_request_timeout", ?tp(
#{pid => Pid, action => Action, stale_channel => stale_channel_info(Pid)}), warning,
"session_stepdown_request_timeout",
#{pid => Pid, action => Action, stale_channel => stale_channel_info(Pid)}
),
ok = force_kill(Pid), ok = force_kill(Pid),
{error, timeout}; {error, timeout};
_ : Error : St -> _:Error:St ->
?tp(error, "session_stepdown_request_exception", ?tp(
#{pid => Pid, action => Action, reason => Error, stacktrace => St, error,
stale_channel => stale_channel_info(Pid)}), "session_stepdown_request_exception",
#{
pid => Pid,
action => Action,
reason => Error,
stacktrace => St,
stale_channel => stale_channel_info(Pid)
}
),
ok = force_kill(Pid), ok = force_kill(Pid),
{error, unexpected_exception} {error, unexpected_exception}
end, end,
@ -477,33 +530,46 @@ kick_session(Action, ClientId, ChanPid) ->
try try
wrap_rpc(emqx_cm_proto_v1:kick_session(Action, ClientId, ChanPid)) wrap_rpc(emqx_cm_proto_v1:kick_session(Action, ClientId, ChanPid))
catch catch
Error : Reason -> Error:Reason ->
%% This should mostly be RPC failures. %% This should mostly be RPC failures.
%% However, if the node is still running the old version %% However, if the node is still running the old version
%% code (prior to emqx app 4.3.10) some of the RPC handler %% code (prior to emqx app 4.3.10) some of the RPC handler
%% exceptions may get propagated to a new version node %% exceptions may get propagated to a new version node
?SLOG(error, #{ msg => "failed_to_kick_session_on_remote_node" ?SLOG(
, node => node(ChanPid) error,
, action => Action #{
, error => Error msg => "failed_to_kick_session_on_remote_node",
, reason => Reason node => node(ChanPid),
}, action => Action,
#{clientid => ClientId}) error => Error,
reason => Reason
},
#{clientid => ClientId}
)
end. end.
kick_session(ClientId) -> kick_session(ClientId) ->
case lookup_channels(ClientId) of case lookup_channels(ClientId) of
[] -> [] ->
?SLOG(warning, #{msg => "kicked_an_unknown_session"}, ?SLOG(
#{clientid => ClientId}), warning,
#{msg => "kicked_an_unknown_session"},
#{clientid => ClientId}
),
ok; ok;
ChanPids -> ChanPids ->
case length(ChanPids) > 1 of case length(ChanPids) > 1 of
true -> true ->
?SLOG(warning, #{msg => "more_than_one_channel_found", ?SLOG(
chan_pids => ChanPids}, warning,
#{clientid => ClientId}); #{
false -> ok msg => "more_than_one_channel_found",
chan_pids => ChanPids
},
#{clientid => ClientId}
);
false ->
ok
end, end,
lists:foreach(fun(Pid) -> kick_session(ClientId, Pid) end, ChanPids) lists:foreach(fun(Pid) -> kick_session(ClientId, Pid) end, ChanPids)
end. end.
@ -514,9 +580,9 @@ kick_session(ClientId) ->
with_channel(ClientId, Fun) -> with_channel(ClientId, Fun) ->
case lookup_channels(ClientId) of case lookup_channels(ClientId) of
[] -> undefined; [] -> undefined;
[Pid] -> Fun(Pid); [Pid] -> Fun(Pid);
Pids -> Fun(lists:last(Pids)) Pids -> Fun(lists:last(Pids))
end. end.
%% @doc Get all registered channel pids. Debug/test interface %% @doc Get all registered channel pids. Debug/test interface
@ -529,14 +595,13 @@ all_client_ids() ->
Pat = [{{'$1', '_'}, [], ['$1']}], Pat = [{{'$1', '_'}, [], ['$1']}],
ets:select(?CHAN_TAB, Pat). ets:select(?CHAN_TAB, Pat).
%% @doc Lookup channels. %% @doc Lookup channels.
-spec(lookup_channels(emqx_types:clientid()) -> list(chan_pid())). -spec lookup_channels(emqx_types:clientid()) -> list(chan_pid()).
lookup_channels(ClientId) -> lookup_channels(ClientId) ->
lookup_channels(global, ClientId). lookup_channels(global, ClientId).
%% @doc Lookup local or global channels. %% @doc Lookup local or global channels.
-spec(lookup_channels(local | global, emqx_types:clientid()) -> list(chan_pid())). -spec lookup_channels(local | global, emqx_types:clientid()) -> list(chan_pid()).
lookup_channels(global, ClientId) -> lookup_channels(global, ClientId) ->
case emqx_cm_registry:is_enabled() of case emqx_cm_registry:is_enabled() of
true -> true ->
@ -544,21 +609,22 @@ lookup_channels(global, ClientId) ->
false -> false ->
lookup_channels(local, ClientId) lookup_channels(local, ClientId)
end; end;
lookup_channels(local, ClientId) -> lookup_channels(local, ClientId) ->
[ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)]. [ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)].
-spec lookup_client({clientid, emqx_types:clientid()} | {username, emqx_types:username()}) -> -spec lookup_client({clientid, emqx_types:clientid()} | {username, emqx_types:username()}) ->
[channel_info()]. [channel_info()].
lookup_client({username, Username}) -> lookup_client({username, Username}) ->
MatchSpec = [{ {'_', #{clientinfo => #{username => '$1'}}, '_'} MatchSpec = [
, [{'=:=','$1', Username}] {{'_', #{clientinfo => #{username => '$1'}}, '_'}, [{'=:=', '$1', Username}], ['$_']}
, ['$_'] ],
}],
ets:select(emqx_channel_info, MatchSpec); ets:select(emqx_channel_info, MatchSpec);
lookup_client({clientid, ClientId}) -> lookup_client({clientid, ClientId}) ->
[Rec || Key <- ets:lookup(emqx_channel, ClientId) [
, Rec <- ets:lookup(emqx_channel_info, Key)]. Rec
|| Key <- ets:lookup(emqx_channel, ClientId),
Rec <- ets:lookup(emqx_channel_info, Key)
].
%% @private %% @private
wrap_rpc(Result) -> wrap_rpc(Result) ->
@ -636,10 +702,12 @@ update_stats({Tab, Stat, MaxStat}) ->
end. end.
-spec do_get_chann_conn_mod(emqx_types:clientid(), chan_pid()) -> -spec do_get_chann_conn_mod(emqx_types:clientid(), chan_pid()) ->
module() | undefined. module() | undefined.
do_get_chann_conn_mod(ClientId, ChanPid) -> do_get_chann_conn_mod(ClientId, ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try [ConnMod] = ets:lookup_element(?CHAN_CONN_TAB, Chan, 2), ConnMod try
[ConnMod] = ets:lookup_element(?CHAN_CONN_TAB, Chan, 2),
ConnMod
catch catch
error:badarg -> undefined error:badarg -> undefined
end. end.

View File

@ -21,46 +21,53 @@
-export([start_link/0]). -export([start_link/0]).
-export([ trans/2 -export([
, trans/3 trans/2,
, lock/1 trans/3,
, lock/2 lock/1,
, unlock/1 lock/2,
]). unlock/1
]).
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
ekka_locker:start_link(?MODULE). ekka_locker:start_link(?MODULE).
-spec(trans(emqx_types:clientid(), fun(([node()]) -> any())) -> any()). -spec trans(emqx_types:clientid(), fun(([node()]) -> any())) -> any().
trans(ClientId, Fun) -> trans(ClientId, Fun) ->
trans(ClientId, Fun, undefined). trans(ClientId, Fun, undefined).
-spec(trans(maybe(emqx_types:clientid()), -spec trans(
fun(([node()])-> any()), ekka_locker:piggyback()) -> any()). maybe(emqx_types:clientid()),
fun(([node()]) -> any()),
ekka_locker:piggyback()
) -> any().
trans(undefined, Fun, _Piggyback) -> trans(undefined, Fun, _Piggyback) ->
Fun([]); Fun([]);
trans(ClientId, Fun, Piggyback) -> trans(ClientId, Fun, Piggyback) ->
case lock(ClientId, Piggyback) of case lock(ClientId, Piggyback) of
{true, Nodes} -> {true, Nodes} ->
try Fun(Nodes) after unlock(ClientId) end; try
Fun(Nodes)
after
unlock(ClientId)
end;
{false, _Nodes} -> {false, _Nodes} ->
{error, client_id_unavailable} {error, client_id_unavailable}
end. end.
-spec(lock(emqx_types:clientid()) -> ekka_locker:lock_result()). -spec lock(emqx_types:clientid()) -> ekka_locker:lock_result().
lock(ClientId) -> lock(ClientId) ->
ekka_locker:acquire(?MODULE, ClientId, strategy()). ekka_locker:acquire(?MODULE, ClientId, strategy()).
-spec(lock(emqx_types:clientid(), ekka_locker:piggyback()) -> ekka_locker:lock_result()). -spec lock(emqx_types:clientid(), ekka_locker:piggyback()) -> ekka_locker:lock_result().
lock(ClientId, Piggyback) -> lock(ClientId, Piggyback) ->
ekka_locker:acquire(?MODULE, ClientId, strategy(), Piggyback). ekka_locker:acquire(?MODULE, ClientId, strategy(), Piggyback).
-spec(unlock(emqx_types:clientid()) -> {boolean(), [node()]}). -spec unlock(emqx_types:clientid()) -> {boolean(), [node()]}.
unlock(ClientId) -> unlock(ClientId) ->
ekka_locker:release(?MODULE, ClientId, strategy()). ekka_locker:release(?MODULE, ClientId, strategy()).
-spec(strategy() -> local | leader | quorum | all). -spec strategy() -> local | leader | quorum | all.
strategy() -> strategy() ->
emqx:get_config([broker, session_locking_strategy]). emqx:get_config([broker, session_locking_strategy]).

View File

@ -23,25 +23,26 @@
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
-export([start_link/0]). -export([start_link/0]).
-export([is_enabled/0]). -export([is_enabled/0]).
-export([ register_channel/1 -export([
, unregister_channel/1 register_channel/1,
]). unregister_channel/1
]).
-export([lookup_channels/1]). -export([lookup_channels/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-define(REGISTRY, ?MODULE). -define(REGISTRY, ?MODULE).
-define(TAB, emqx_channel_registry). -define(TAB, emqx_channel_registry).
@ -50,7 +51,7 @@
-record(channel, {chid, pid}). -record(channel, {chid, pid}).
%% @doc Start the global channel registry. %% @doc Start the global channel registry.
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []). gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []).
@ -59,16 +60,17 @@ start_link() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Is the global registry enabled? %% @doc Is the global registry enabled?
-spec(is_enabled() -> boolean()). -spec is_enabled() -> boolean().
is_enabled() -> is_enabled() ->
emqx:get_config([broker, enable_session_registry]). emqx:get_config([broker, enable_session_registry]).
%% @doc Register a global channel. %% @doc Register a global channel.
-spec(register_channel(emqx_types:clientid() -spec register_channel(
| {emqx_types:clientid(), pid()}) -> ok). emqx_types:clientid()
| {emqx_types:clientid(), pid()}
) -> ok.
register_channel(ClientId) when is_binary(ClientId) -> register_channel(ClientId) when is_binary(ClientId) ->
register_channel({ClientId, self()}); register_channel({ClientId, self()});
register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) -> register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
case is_enabled() of case is_enabled() of
true -> mria:dirty_write(?TAB, record(ClientId, ChanPid)); true -> mria:dirty_write(?TAB, record(ClientId, ChanPid));
@ -76,11 +78,12 @@ register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid)
end. end.
%% @doc Unregister a global channel. %% @doc Unregister a global channel.
-spec(unregister_channel(emqx_types:clientid() -spec unregister_channel(
| {emqx_types:clientid(), pid()}) -> ok). emqx_types:clientid()
| {emqx_types:clientid(), pid()}
) -> ok.
unregister_channel(ClientId) when is_binary(ClientId) -> unregister_channel(ClientId) when is_binary(ClientId) ->
unregister_channel({ClientId, self()}); unregister_channel({ClientId, self()});
unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) -> unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
case is_enabled() of case is_enabled() of
true -> mria:dirty_delete_object(?TAB, record(ClientId, ChanPid)); true -> mria:dirty_delete_object(?TAB, record(ClientId, ChanPid));
@ -88,7 +91,7 @@ unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid
end. end.
%% @doc Lookup the global channels. %% @doc Lookup the global channels.
-spec(lookup_channels(emqx_types:clientid()) -> list(pid())). -spec lookup_channels(emqx_types:clientid()) -> list(pid()).
lookup_channels(ClientId) -> lookup_channels(ClientId) ->
[ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)]. [ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)].
@ -102,13 +105,18 @@ record(ClientId, ChanPid) ->
init([]) -> init([]) ->
mria_config:set_dirty_shard(?CM_SHARD, true), mria_config:set_dirty_shard(?CM_SHARD, true),
ok = mria:create_table(?TAB, [ ok = mria:create_table(?TAB, [
{type, bag}, {type, bag},
{rlog_shard, ?CM_SHARD}, {rlog_shard, ?CM_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, channel}, {record_name, channel},
{attributes, record_info(fields, channel)}, {attributes, record_info(fields, channel)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]), {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]),
ok = mria_rlog:wait_for_shards([?CM_SHARD], infinity), ok = mria_rlog:wait_for_shards([?CM_SHARD], infinity),
ok = ekka:monitor(membership), ok = ekka:monitor(membership),
{ok, #{}}. {ok, #{}}.
@ -124,14 +132,11 @@ handle_cast(Msg, State) ->
handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, {mnesia, down, Node}}, State) ->
cleanup_channels(Node), cleanup_channels(Node),
{noreply, State}; {noreply, State};
handle_info({membership, {node, down, Node}}, State) -> handle_info({membership, {node, down, Node}}, State) ->
cleanup_channels(Node), cleanup_channels(Node),
{noreply, State}; {noreply, State};
handle_info({membership, _Event}, State) -> handle_info({membership, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -147,10 +152,12 @@ code_change(_OldVsn, State, _Extra) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
cleanup_channels(Node) -> cleanup_channels(Node) ->
global:trans({?LOCK, self()}, global:trans(
fun() -> {?LOCK, self()},
mria:transaction(?CM_SHARD, fun do_cleanup_channels/1, [Node]) fun() ->
end). mria:transaction(?CM_SHARD, fun do_cleanup_channels/1, [Node])
end
).
do_cleanup_channels(Node) -> do_cleanup_channels(Node) ->
Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}], Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}],

View File

@ -34,15 +34,16 @@ start_link() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
SupFlags = #{strategy => one_for_one, SupFlags = #{
intensity => 100, strategy => one_for_one,
period => 10 intensity => 100,
}, period => 10
Banned = child_spec(emqx_banned, 1000, worker), },
Banned = child_spec(emqx_banned, 1000, worker),
Flapping = child_spec(emqx_flapping, 1000, worker), Flapping = child_spec(emqx_flapping, 1000, worker),
Locker = child_spec(emqx_cm_locker, 5000, worker), Locker = child_spec(emqx_cm_locker, 5000, worker),
Registry = child_spec(emqx_cm_registry, 5000, worker), Registry = child_spec(emqx_cm_registry, 5000, worker),
Manager = child_spec(emqx_cm, 5000, worker), Manager = child_spec(emqx_cm, 5000, worker),
{ok, {SupFlags, [Banned, Flapping, Locker, Registry, Manager]}}. {ok, {SupFlags, [Banned, Flapping, Locker, Registry, Manager]}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -50,10 +51,11 @@ init([]) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
child_spec(Mod, Shutdown, Type) -> child_spec(Mod, Shutdown, Type) ->
#{id => Mod, #{
start => {Mod, start_link, []}, id => Mod,
restart => permanent, start => {Mod, start_link, []},
shutdown => Shutdown, restart => permanent,
type => Type, shutdown => Shutdown,
modules => [Mod] type => Type,
}. modules => [Mod]
}.

View File

@ -18,61 +18,68 @@
-compile({no_auto_import, [get/0, get/1, put/2, erase/1]}). -compile({no_auto_import, [get/0, get/1, put/2, erase/1]}).
-elvis([{elvis_style, god_modules, disable}]). -elvis([{elvis_style, god_modules, disable}]).
-export([ init_load/1 -export([
, init_load/2 init_load/1,
, read_override_conf/1 init_load/2,
, delete_override_conf_files/0 read_override_conf/1,
, check_config/2 delete_override_conf_files/0,
, fill_defaults/1 check_config/2,
, fill_defaults/2 fill_defaults/1,
, save_configs/5 fill_defaults/2,
, save_to_app_env/1 save_configs/5,
, save_to_config_map/2 save_to_app_env/1,
, save_to_override_conf/2 save_to_config_map/2,
]). save_to_override_conf/2
]).
-export([ get_root/1 -export([
, get_root_raw/1 get_root/1,
]). get_root_raw/1
]).
-export([ get_default_value/1 -export([get_default_value/1]).
]).
-export([ get/1 -export([
, get/2 get/1,
, find/1 get/2,
, find_raw/1 find/1,
, put/1 find_raw/1,
, put/2 put/1,
, erase/1 put/2,
]). erase/1
]).
-export([ get_raw/1 -export([
, get_raw/2 get_raw/1,
, put_raw/1 get_raw/2,
, put_raw/2 put_raw/1,
]). put_raw/2
]).
-export([ save_schema_mod_and_names/1 -export([
, get_schema_mod/0 save_schema_mod_and_names/1,
, get_schema_mod/1 get_schema_mod/0,
, get_root_names/0 get_schema_mod/1,
]). get_root_names/0
]).
-export([ get_zone_conf/2 -export([
, get_zone_conf/3 get_zone_conf/2,
, put_zone_conf/3 get_zone_conf/3,
]). put_zone_conf/3
]).
-export([ get_listener_conf/3 -export([
, get_listener_conf/4 get_listener_conf/3,
, put_listener_conf/4 get_listener_conf/4,
, find_listener_conf/3 put_listener_conf/4,
]). find_listener_conf/3
]).
-export([ add_handlers/0 -export([
, remove_handlers/0 add_handlers/0,
]). remove_handlers/0
]).
-include("logger.hrl"). -include("logger.hrl").
@ -88,25 +95,34 @@
AtomKeyPath -> EXP AtomKeyPath -> EXP
catch catch
error:badarg -> EXP_ON_FAIL error:badarg -> EXP_ON_FAIL
end). end
).
-export_type([update_request/0, raw_config/0, config/0, app_envs/0, -export_type([
update_opts/0, update_cmd/0, update_args/0, update_request/0,
update_error/0, update_result/0]). raw_config/0,
config/0,
app_envs/0,
update_opts/0,
update_cmd/0,
update_args/0,
update_error/0,
update_result/0
]).
-type update_request() :: term(). -type update_request() :: term().
-type update_cmd() :: {update, update_request()} | remove. -type update_cmd() :: {update, update_request()} | remove.
-type update_opts() :: #{ -type update_opts() :: #{
%% rawconf_with_defaults: %% rawconf_with_defaults:
%% fill the default values into the `raw_config` field of the return value %% fill the default values into the `raw_config` field of the return value
%% defaults to `false` %% defaults to `false`
rawconf_with_defaults => boolean(), rawconf_with_defaults => boolean(),
%% persistent: %% persistent:
%% save the updated config to the emqx_override.conf file %% save the updated config to the emqx_override.conf file
%% defaults to `true` %% defaults to `true`
persistent => boolean(), persistent => boolean(),
override_to => local | cluster override_to => local | cluster
}. }.
-type update_args() :: {update_cmd(), Opts :: update_opts()}. -type update_args() :: {update_cmd(), Opts :: update_opts()}.
-type update_stage() :: pre_config_update | post_config_update. -type update_stage() :: pre_config_update | post_config_update.
-type update_error() :: {update_stage(), module(), term()} | {save_configs, term()} | term(). -type update_error() :: {update_stage(), module(), term()} | {save_configs, term()} | term().
@ -149,8 +165,11 @@ find([]) ->
Res -> {ok, Res} Res -> {ok, Res}
end; end;
find(KeyPath) -> find(KeyPath) ->
?ATOM_CONF_PATH(KeyPath, emqx_map_lib:deep_find(AtomKeyPath, get_root(KeyPath)), ?ATOM_CONF_PATH(
{not_found, KeyPath}). KeyPath,
emqx_map_lib:deep_find(AtomKeyPath, get_root(KeyPath)),
{not_found, KeyPath}
).
-spec find_raw(emqx_map_lib:config_key_path()) -> -spec find_raw(emqx_map_lib:config_key_path()) ->
{ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}. {ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}.
@ -166,17 +185,21 @@ find_raw(KeyPath) ->
-spec get_zone_conf(atom(), emqx_map_lib:config_key_path()) -> term(). -spec get_zone_conf(atom(), emqx_map_lib:config_key_path()) -> term().
get_zone_conf(Zone, KeyPath) -> get_zone_conf(Zone, KeyPath) ->
case find(?ZONE_CONF_PATH(Zone, KeyPath)) of case find(?ZONE_CONF_PATH(Zone, KeyPath)) of
{not_found, _, _} -> %% not found in zones, try to find the global config %% not found in zones, try to find the global config
{not_found, _, _} ->
?MODULE:get(KeyPath); ?MODULE:get(KeyPath);
{ok, Value} -> Value {ok, Value} ->
Value
end. end.
-spec get_zone_conf(atom(), emqx_map_lib:config_key_path(), term()) -> term(). -spec get_zone_conf(atom(), emqx_map_lib:config_key_path(), term()) -> term().
get_zone_conf(Zone, KeyPath, Default) -> get_zone_conf(Zone, KeyPath, Default) ->
case find(?ZONE_CONF_PATH(Zone, KeyPath)) of case find(?ZONE_CONF_PATH(Zone, KeyPath)) of
{not_found, _, _} -> %% not found in zones, try to find the global config %% not found in zones, try to find the global config
{not_found, _, _} ->
?MODULE:get(KeyPath, Default); ?MODULE:get(KeyPath, Default);
{ok, Value} -> Value {ok, Value} ->
Value
end. end.
-spec put_zone_conf(atom(), emqx_map_lib:config_key_path(), term()) -> ok. -spec put_zone_conf(atom(), emqx_map_lib:config_key_path(), term()) -> ok.
@ -202,9 +225,13 @@ find_listener_conf(Type, Listener, KeyPath) ->
-spec put(map()) -> ok. -spec put(map()) -> ok.
put(Config) -> put(Config) ->
maps:fold(fun(RootName, RootValue, _) -> maps:fold(
fun(RootName, RootValue, _) ->
?MODULE:put([RootName], RootValue) ?MODULE:put([RootName], RootValue)
end, ok, Config). end,
ok,
Config
).
erase(RootName) -> erase(RootName) ->
persistent_term:erase(?PERSIS_KEY(?CONF, bin(RootName))), persistent_term:erase(?PERSIS_KEY(?CONF, bin(RootName))),
@ -219,12 +246,14 @@ get_default_value([RootName | _] = KeyPath) ->
case find_raw([RootName]) of case find_raw([RootName]) of
{ok, RawConf} -> {ok, RawConf} ->
RawConf1 = emqx_map_lib:deep_remove(BinKeyPath, #{bin(RootName) => RawConf}), RawConf1 = emqx_map_lib:deep_remove(BinKeyPath, #{bin(RootName) => RawConf}),
try fill_defaults(get_schema_mod(RootName), RawConf1) of FullConf -> try fill_defaults(get_schema_mod(RootName), RawConf1) of
case emqx_map_lib:deep_find(BinKeyPath, FullConf) of FullConf ->
{not_found, _, _} -> {error, no_default_value}; case emqx_map_lib:deep_find(BinKeyPath, FullConf) of
{ok, Val} -> {ok, Val} {not_found, _, _} -> {error, no_default_value};
end {ok, Val} -> {ok, Val}
catch error : Reason -> {error, Reason} end
catch
error:Reason -> {error, Reason}
end; end;
{not_found, _, _} -> {not_found, _, _} ->
{error, {rootname_not_found, RootName}} {error, {rootname_not_found, RootName}}
@ -238,9 +267,13 @@ get_raw(KeyPath, Default) -> do_get(?RAW_CONF, KeyPath, Default).
-spec put_raw(map()) -> ok. -spec put_raw(map()) -> ok.
put_raw(Config) -> put_raw(Config) ->
maps:fold(fun(RootName, RootV, _) -> maps:fold(
fun(RootName, RootV, _) ->
?MODULE:put_raw([RootName], RootV) ?MODULE:put_raw([RootName], RootV)
end, ok, hocon_maps:ensure_plain(Config)). end,
ok,
hocon_maps:ensure_plain(Config)
).
-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok. -spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
put_raw(KeyPath, Config) -> do_put(?RAW_CONF, KeyPath, Config). put_raw(KeyPath, Config) -> do_put(?RAW_CONF, KeyPath, Config).
@ -268,10 +301,12 @@ init_load(SchemaMod, RawConf) when is_map(RawConf) ->
RawConfWithOverrides = hocon:deep_merge(RawConfWithEnvs, Overrides), RawConfWithOverrides = hocon:deep_merge(RawConfWithEnvs, Overrides),
%% check configs against the schema %% check configs against the schema
{_AppEnvs, CheckedConf} = {_AppEnvs, CheckedConf} =
check_config(SchemaMod, RawConfWithOverrides , #{}), check_config(SchemaMod, RawConfWithOverrides, #{}),
RootNames = get_root_names(), RootNames = get_root_names(),
ok = save_to_config_map(maps:with(get_atom_root_names(), CheckedConf), ok = save_to_config_map(
maps:with(RootNames, RawConfWithOverrides)). maps:with(get_atom_root_names(), CheckedConf),
maps:with(RootNames, RawConfWithOverrides)
).
parse_hocon(Conf) -> parse_hocon(Conf) ->
IncDirs = include_dirs(), IncDirs = include_dirs(),
@ -279,12 +314,13 @@ parse_hocon(Conf) ->
{ok, HoconMap} -> {ok, HoconMap} ->
HoconMap; HoconMap;
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "failed_to_load_hocon_conf", ?SLOG(error, #{
reason => Reason, msg => "failed_to_load_hocon_conf",
pwd => file:get_cwd(), reason => Reason,
include_dirs => IncDirs, pwd => file:get_cwd(),
config_file => Conf include_dirs => IncDirs,
}), config_file => Conf
}),
error(failed_to_load_hocon_conf) error(failed_to_load_hocon_conf)
end. end.
@ -299,22 +335,26 @@ include_dirs() ->
[filename:join(emqx:data_dir(), "configs")]. [filename:join(emqx:data_dir(), "configs")].
merge_envs(SchemaMod, RawConf) -> merge_envs(SchemaMod, RawConf) ->
Opts = #{required => false, %% TODO: evil, remove, required should be declared in schema %% TODO: evil, remove, required should be declared in schema
format => map, Opts = #{
apply_override_envs => true required => false,
}, format => map,
apply_override_envs => true
},
hocon_tconf:merge_env_overrides(SchemaMod, RawConf, all, Opts). hocon_tconf:merge_env_overrides(SchemaMod, RawConf, all, Opts).
-spec check_config(hocon_schema:schema(), raw_config()) -> {AppEnvs, CheckedConf} -spec check_config(hocon_schema:schema(), raw_config()) -> {AppEnvs, CheckedConf} when
when AppEnvs :: app_envs(), CheckedConf :: config(). AppEnvs :: app_envs(), CheckedConf :: config().
check_config(SchemaMod, RawConf) -> check_config(SchemaMod, RawConf) ->
check_config(SchemaMod, RawConf, #{}). check_config(SchemaMod, RawConf, #{}).
check_config(SchemaMod, RawConf, Opts0) -> check_config(SchemaMod, RawConf, Opts0) ->
Opts1 = #{return_plain => true, Opts1 = #{
required => false, %% TODO: evil, remove, required should be declared in schema return_plain => true,
format => map %% TODO: evil, remove, required should be declared in schema
}, required => false,
format => map
},
Opts = maps:merge(Opts0, Opts1), Opts = maps:merge(Opts0, Opts1),
{AppEnvs, CheckedConf} = {AppEnvs, CheckedConf} =
hocon_tconf:map_translate(SchemaMod, RawConf, Opts), hocon_tconf:map_translate(SchemaMod, RawConf, Opts),
@ -323,21 +363,28 @@ check_config(SchemaMod, RawConf, Opts0) ->
-spec fill_defaults(raw_config()) -> map(). -spec fill_defaults(raw_config()) -> map().
fill_defaults(RawConf) -> fill_defaults(RawConf) ->
RootNames = get_root_names(), RootNames = get_root_names(),
maps:fold(fun(Key, Conf, Acc) -> maps:fold(
fun(Key, Conf, Acc) ->
SubMap = #{Key => Conf}, SubMap = #{Key => Conf},
WithDefaults = case lists:member(Key, RootNames) of WithDefaults =
true -> fill_defaults(get_schema_mod(Key), SubMap); case lists:member(Key, RootNames) of
false -> SubMap true -> fill_defaults(get_schema_mod(Key), SubMap);
end, false -> SubMap
end,
maps:merge(Acc, WithDefaults) maps:merge(Acc, WithDefaults)
end, #{}, RawConf). end,
#{},
RawConf
).
-spec fill_defaults(module(), raw_config()) -> map(). -spec fill_defaults(module(), raw_config()) -> map().
fill_defaults(SchemaMod, RawConf) -> fill_defaults(SchemaMod, RawConf) ->
hocon_tconf:check_plain(SchemaMod, RawConf, hocon_tconf:check_plain(
SchemaMod,
RawConf,
#{required => false, only_fill_defaults => true}, #{required => false, only_fill_defaults => true},
root_names_from_conf(RawConf)). root_names_from_conf(RawConf)
).
%% @doc Only for test cleanups. %% @doc Only for test cleanups.
%% Delete override config files. %% Delete override config files.
@ -408,9 +455,12 @@ save_configs(_AppEnvs, Conf, RawConf, OverrideConf, Opts) ->
-spec save_to_app_env([tuple()]) -> ok. -spec save_to_app_env([tuple()]) -> ok.
save_to_app_env(AppEnvs) -> save_to_app_env(AppEnvs) ->
lists:foreach(fun({AppName, Envs}) -> lists:foreach(
fun({AppName, Envs}) ->
[application:set_env(AppName, Par, Val) || {Par, Val} <- Envs] [application:set_env(AppName, Par, Val) || {Par, Val} <- Envs]
end, AppEnvs). end,
AppEnvs
).
-spec save_to_config_map(config(), raw_config()) -> ok. -spec save_to_config_map(config(), raw_config()) -> ok.
save_to_config_map(Conf, RawConf) -> save_to_config_map(Conf, RawConf) ->
@ -422,15 +472,19 @@ save_to_override_conf(undefined, _) ->
ok; ok;
save_to_override_conf(RawConf, Opts) -> save_to_override_conf(RawConf, Opts) ->
case override_conf_file(Opts) of case override_conf_file(Opts) of
undefined -> ok; undefined ->
ok;
FileName -> FileName ->
ok = filelib:ensure_dir(FileName), ok = filelib:ensure_dir(FileName),
case file:write_file(FileName, hocon_pp:do(RawConf, #{})) of case file:write_file(FileName, hocon_pp:do(RawConf, #{})) of
ok -> ok; ok ->
ok;
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "failed_to_write_override_file", ?SLOG(error, #{
filename => FileName, msg => "failed_to_write_override_file",
reason => Reason}), filename => FileName,
reason => Reason
}),
{error, Reason} {error, Reason}
end end
end. end.
@ -449,7 +503,8 @@ load_hocon_file(FileName, LoadType) ->
Opts = #{include_dirs => include_dirs(), format => LoadType}, Opts = #{include_dirs => include_dirs(), format => LoadType},
{ok, Raw0} = hocon:load(FileName, Opts), {ok, Raw0} = hocon:load(FileName, Opts),
Raw0; Raw0;
false -> #{} false ->
#{}
end. end.
do_get(Type, KeyPath) -> do_get(Type, KeyPath) ->
@ -461,11 +516,16 @@ do_get(Type, KeyPath) ->
end. end.
do_get(Type, [], Default) -> do_get(Type, [], Default) ->
AllConf = lists:foldl(fun AllConf = lists:foldl(
fun
({?PERSIS_KEY(Type0, RootName), Conf}, AccIn) when Type0 == Type -> ({?PERSIS_KEY(Type0, RootName), Conf}, AccIn) when Type0 == Type ->
AccIn#{conf_key(Type0, RootName) => Conf}; AccIn#{conf_key(Type0, RootName) => Conf};
(_, AccIn) -> AccIn (_, AccIn) ->
end, #{}, persistent_term:get()), AccIn
end,
#{},
persistent_term:get()
),
case AllConf =:= #{} of case AllConf =:= #{} of
true -> Default; true -> Default;
false -> AllConf false -> AllConf
@ -477,23 +537,33 @@ do_get(Type, [RootName | KeyPath], Default) ->
do_deep_get(Type, KeyPath, RootV, Default). do_deep_get(Type, KeyPath, RootV, Default).
do_put(Type, [], DeepValue) -> do_put(Type, [], DeepValue) ->
maps:fold(fun(RootName, Value, _Res) -> maps:fold(
fun(RootName, Value, _Res) ->
do_put(Type, [RootName], Value) do_put(Type, [RootName], Value)
end, ok, DeepValue); end,
ok,
DeepValue
);
do_put(Type, [RootName | KeyPath], DeepValue) -> do_put(Type, [RootName | KeyPath], DeepValue) ->
OldValue = do_get(Type, [RootName], #{}), OldValue = do_get(Type, [RootName], #{}),
NewValue = do_deep_put(Type, KeyPath, OldValue, DeepValue), NewValue = do_deep_put(Type, KeyPath, OldValue, DeepValue),
persistent_term:put(?PERSIS_KEY(Type, bin(RootName)), NewValue). persistent_term:put(?PERSIS_KEY(Type, bin(RootName)), NewValue).
do_deep_get(?CONF, KeyPath, Map, Default) -> do_deep_get(?CONF, KeyPath, Map, Default) ->
?ATOM_CONF_PATH(KeyPath, emqx_map_lib:deep_get(AtomKeyPath, Map, Default), ?ATOM_CONF_PATH(
Default); KeyPath,
emqx_map_lib:deep_get(AtomKeyPath, Map, Default),
Default
);
do_deep_get(?RAW_CONF, KeyPath, Map, Default) -> do_deep_get(?RAW_CONF, KeyPath, Map, Default) ->
emqx_map_lib:deep_get([bin(Key) || Key <- KeyPath], Map, Default). emqx_map_lib:deep_get([bin(Key) || Key <- KeyPath], Map, Default).
do_deep_put(?CONF, KeyPath, Map, Value) -> do_deep_put(?CONF, KeyPath, Map, Value) ->
?ATOM_CONF_PATH(KeyPath, emqx_map_lib:deep_put(AtomKeyPath, Map, Value), ?ATOM_CONF_PATH(
error({not_found, KeyPath})); KeyPath,
emqx_map_lib:deep_put(AtomKeyPath, Map, Value),
error({not_found, KeyPath})
);
do_deep_put(?RAW_CONF, KeyPath, Map, Value) -> do_deep_put(?RAW_CONF, KeyPath, Map, Value) ->
emqx_map_lib:deep_put([bin(Key) || Key <- KeyPath], Map, Value). emqx_map_lib:deep_put([bin(Key) || Key <- KeyPath], Map, Value).

View File

@ -23,23 +23,26 @@
-behaviour(gen_server). -behaviour(gen_server).
%% API functions %% API functions
-export([ start_link/0 -export([
, stop/0 start_link/0,
, add_handler/2 stop/0,
, remove_handler/1 add_handler/2,
, update_config/3 remove_handler/1,
, get_raw_cluster_override_conf/0 update_config/3,
, info/0 get_raw_cluster_override_conf/0,
, merge_to_old_config/2 info/0,
]). merge_to_old_config/2
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
handle_call/3, init/1,
handle_cast/2, handle_call/3,
handle_info/2, handle_cast/2,
terminate/2, handle_info/2,
code_change/3]). terminate/2,
code_change/3
]).
-define(MOD, {mod}). -define(MOD, {mod}).
-define(WKEY, '?'). -define(WKEY, '?').
@ -47,16 +50,22 @@
-type handler_name() :: module(). -type handler_name() :: module().
-type handlers() :: #{emqx_config:config_key() => handlers(), ?MOD => handler_name()}. -type handlers() :: #{emqx_config:config_key() => handlers(), ?MOD => handler_name()}.
-optional_callbacks([ pre_config_update/3 -optional_callbacks([
, post_config_update/5 pre_config_update/3,
]). post_config_update/5
]).
-callback pre_config_update([atom()], emqx_config:update_request(), emqx_config:raw_config()) -> -callback pre_config_update([atom()], emqx_config:update_request(), emqx_config:raw_config()) ->
{ok, emqx_config:update_request()} | {error, term()}. {ok, emqx_config:update_request()} | {error, term()}.
-callback post_config_update([atom()], emqx_config:update_request(), emqx_config:config(), -callback post_config_update(
emqx_config:config(), emqx_config:app_envs()) -> [atom()],
ok | {ok, Result::any()} | {error, Reason::term()}. emqx_config:update_request(),
emqx_config:config(),
emqx_config:config(),
emqx_config:app_envs()
) ->
ok | {ok, Result :: any()} | {error, Reason :: term()}.
-type state() :: #{ -type state() :: #{
handlers := handlers(), handlers := handlers(),
@ -106,9 +115,11 @@ handle_call({add_handler, ConfKeyPath, HandlerName}, _From, State = #{handlers :
{ok, NewHandlers} -> {reply, ok, State#{handlers => NewHandlers}}; {ok, NewHandlers} -> {reply, ok, State#{handlers => NewHandlers}};
{error, _Reason} = Error -> {reply, Error, State} {error, _Reason} = Error -> {reply, Error, State}
end; end;
handle_call(
handle_call({change_config, SchemaModule, ConfKeyPath, UpdateArgs}, _From, {change_config, SchemaModule, ConfKeyPath, UpdateArgs},
#{handlers := Handlers} = State) -> _From,
#{handlers := Handlers} = State
) ->
Result = handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs), Result = handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs),
{reply, Result, State}; {reply, Result, State};
handle_call(get_raw_cluster_override_conf, _From, State) -> handle_call(get_raw_cluster_override_conf, _From, State) ->
@ -140,13 +151,14 @@ deep_put_handler([], Handlers, Mod) ->
deep_put_handler([Key | KeyPath], Handlers, Mod) -> deep_put_handler([Key | KeyPath], Handlers, Mod) ->
SubHandlers = maps:get(Key, Handlers, #{}), SubHandlers = maps:get(Key, Handlers, #{}),
case deep_put_handler(KeyPath, SubHandlers, Mod) of case deep_put_handler(KeyPath, SubHandlers, Mod) of
{ok, NewSubHandlers} -> {ok, NewSubHandlers} ->
NewHandlers = Handlers#{Key => NewSubHandlers}, NewHandlers = Handlers#{Key => NewSubHandlers},
case check_handler_conflict(NewHandlers) of case check_handler_conflict(NewHandlers) of
ok -> {ok, NewHandlers}; ok -> {ok, NewHandlers};
{error, Reason} -> {error, Reason} {error, Reason} -> {error, Reason}
end; end;
{error, _Reason} = Error -> Error {error, _Reason} = Error ->
Error
end. end.
%% Make sure that Specify Key and ?WKEY cannot be on the same level. %% Make sure that Specify Key and ?WKEY cannot be on the same level.
@ -165,34 +177,45 @@ check_handler_conflict(Handlers) ->
filter_top_level_handlers(Handlers) -> filter_top_level_handlers(Handlers) ->
maps:fold( maps:fold(
fun fun
(K, #{?MOD := _}, Acc) -> [K | Acc]; (K, #{?MOD := _}, Acc) -> [K | Acc];
(_K, #{}, Acc) -> Acc; (_K, #{}, Acc) -> Acc;
(?MOD, _, Acc) -> Acc (?MOD, _, Acc) -> Acc
end, [], Handlers). end,
[],
Handlers
).
handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) -> handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) ->
try try
do_handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) do_handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs)
catch catch
throw : Reason -> throw:Reason ->
{error, Reason}; {error, Reason};
Error : Reason : ST -> Error:Reason:ST ->
?SLOG(error, #{msg => "change_config_crashed", ?SLOG(error, #{
exception => Error, msg => "change_config_crashed",
reason => Reason, exception => Error,
update_req => UpdateArgs, reason => Reason,
module => SchemaModule, update_req => UpdateArgs,
key_path => ConfKeyPath, module => SchemaModule,
stacktrace => ST key_path => ConfKeyPath,
}), stacktrace => ST
}),
{error, config_update_crashed} {error, config_update_crashed}
end. end.
do_handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) -> do_handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) ->
case process_update_request(ConfKeyPath, Handlers, UpdateArgs) of case process_update_request(ConfKeyPath, Handlers, UpdateArgs) of
{ok, NewRawConf, OverrideConf, Opts} -> {ok, NewRawConf, OverrideConf, Opts} ->
check_and_save_configs(SchemaModule, ConfKeyPath, Handlers, NewRawConf, check_and_save_configs(
OverrideConf, UpdateArgs, Opts); SchemaModule,
ConfKeyPath,
Handlers,
NewRawConf,
OverrideConf,
UpdateArgs,
Opts
);
{error, Result} -> {error, Result} ->
{error, Result} {error, Result}
end. end.
@ -211,7 +234,8 @@ process_update_request(ConfKeyPath, Handlers, {{update, UpdateReq}, Opts}) ->
{ok, NewRawConf} -> {ok, NewRawConf} ->
OverrideConf = update_override_config(NewRawConf, Opts), OverrideConf = update_override_config(NewRawConf, Opts),
{ok, NewRawConf, OverrideConf, Opts}; {ok, NewRawConf, OverrideConf, Opts};
Error -> Error Error ->
Error
end. end.
do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq) -> do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq) ->
@ -219,8 +243,13 @@ do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq) ->
do_update_config([], Handlers, OldRawConf, UpdateReq, ConfKeyPath) -> do_update_config([], Handlers, OldRawConf, UpdateReq, ConfKeyPath) ->
call_pre_config_update(Handlers, OldRawConf, UpdateReq, ConfKeyPath); call_pre_config_update(Handlers, OldRawConf, UpdateReq, ConfKeyPath);
do_update_config([ConfKey | SubConfKeyPath], Handlers, OldRawConf, do_update_config(
UpdateReq, ConfKeyPath0) -> [ConfKey | SubConfKeyPath],
Handlers,
OldRawConf,
UpdateReq,
ConfKeyPath0
) ->
ConfKeyPath = ConfKeyPath0 ++ [ConfKey], ConfKeyPath = ConfKeyPath0 ++ [ConfKey],
ConfKeyBin = bin(ConfKey), ConfKeyBin = bin(ConfKey),
SubOldRawConf = get_sub_config(ConfKeyBin, OldRawConf), SubOldRawConf = get_sub_config(ConfKeyBin, OldRawConf),
@ -230,43 +259,110 @@ do_update_config([ConfKey | SubConfKeyPath], Handlers, OldRawConf,
Error -> Error Error -> Error
end. end.
check_and_save_configs(SchemaModule, ConfKeyPath, Handlers, NewRawConf, OverrideConf, check_and_save_configs(
UpdateArgs, Opts) -> SchemaModule,
ConfKeyPath,
Handlers,
NewRawConf,
OverrideConf,
UpdateArgs,
Opts
) ->
OldConf = emqx_config:get_root(ConfKeyPath), OldConf = emqx_config:get_root(ConfKeyPath),
Schema = schema(SchemaModule, ConfKeyPath), Schema = schema(SchemaModule, ConfKeyPath),
{AppEnvs, NewConf} = emqx_config:check_config(Schema, NewRawConf), {AppEnvs, NewConf} = emqx_config:check_config(Schema, NewRawConf),
case do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, #{}) of case do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, #{}) of
{ok, Result0} -> {ok, Result0} ->
remove_from_local_if_cluster_change(ConfKeyPath, Opts), remove_from_local_if_cluster_change(ConfKeyPath, Opts),
case save_configs(ConfKeyPath, AppEnvs, NewConf, NewRawConf, OverrideConf, case
UpdateArgs, Opts) of save_configs(
ConfKeyPath,
AppEnvs,
NewConf,
NewRawConf,
OverrideConf,
UpdateArgs,
Opts
)
of
{ok, Result1} -> {ok, Result1} ->
{ok, Result1#{post_config_update => Result0}}; {ok, Result1#{post_config_update => Result0}};
Error -> Error Error ->
Error
end; end;
Error -> Error Error ->
Error
end. end.
do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, Result) -> do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, Result) ->
do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, do_post_config_update(
Result, []). ConfKeyPath,
Handlers,
OldConf,
NewConf,
AppEnvs,
UpdateArgs,
Result,
[]
).
do_post_config_update([], Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, Result, do_post_config_update(
ConfKeyPath) -> [],
call_post_config_update(Handlers, OldConf, NewConf, AppEnvs, up_req(UpdateArgs), Handlers,
Result, ConfKeyPath); OldConf,
do_post_config_update([ConfKey | SubConfKeyPath], Handlers, OldConf, NewConf, AppEnvs, NewConf,
UpdateArgs, Result, ConfKeyPath0) -> AppEnvs,
UpdateArgs,
Result,
ConfKeyPath
) ->
call_post_config_update(
Handlers,
OldConf,
NewConf,
AppEnvs,
up_req(UpdateArgs),
Result,
ConfKeyPath
);
do_post_config_update(
[ConfKey | SubConfKeyPath],
Handlers,
OldConf,
NewConf,
AppEnvs,
UpdateArgs,
Result,
ConfKeyPath0
) ->
ConfKeyPath = ConfKeyPath0 ++ [ConfKey], ConfKeyPath = ConfKeyPath0 ++ [ConfKey],
SubOldConf = get_sub_config(ConfKey, OldConf), SubOldConf = get_sub_config(ConfKey, OldConf),
SubNewConf = get_sub_config(ConfKey, NewConf), SubNewConf = get_sub_config(ConfKey, NewConf),
SubHandlers = get_sub_handlers(ConfKey, Handlers), SubHandlers = get_sub_handlers(ConfKey, Handlers),
case do_post_config_update(SubConfKeyPath, SubHandlers, SubOldConf, SubNewConf, AppEnvs, case
UpdateArgs, Result, ConfKeyPath) of do_post_config_update(
SubConfKeyPath,
SubHandlers,
SubOldConf,
SubNewConf,
AppEnvs,
UpdateArgs,
Result,
ConfKeyPath
)
of
{ok, Result1} -> {ok, Result1} ->
call_post_config_update(Handlers, OldConf, NewConf, AppEnvs, up_req(UpdateArgs), call_post_config_update(
Result1, ConfKeyPath); Handlers,
Error -> Error OldConf,
NewConf,
AppEnvs,
up_req(UpdateArgs),
Result1,
ConfKeyPath
);
Error ->
Error
end. end.
get_sub_handlers(ConfKey, Handlers) -> get_sub_handlers(ConfKey, Handlers) ->
@ -277,7 +373,8 @@ get_sub_handlers(ConfKey, Handlers) ->
get_sub_config(ConfKey, Conf) when is_map(Conf) -> get_sub_config(ConfKey, Conf) when is_map(Conf) ->
maps:get(ConfKey, Conf, undefined); maps:get(ConfKey, Conf, undefined);
get_sub_config(_, _Conf) -> %% the Conf is a primitive %% the Conf is a primitive
get_sub_config(_, _Conf) ->
undefined. undefined.
call_pre_config_update(#{?MOD := HandlerName}, OldRawConf, UpdateReq, ConfKeyPath) -> call_pre_config_update(#{?MOD := HandlerName}, OldRawConf, UpdateReq, ConfKeyPath) ->
@ -287,25 +384,48 @@ call_pre_config_update(#{?MOD := HandlerName}, OldRawConf, UpdateReq, ConfKeyPat
{ok, NewUpdateReq} -> {ok, NewUpdateReq}; {ok, NewUpdateReq} -> {ok, NewUpdateReq};
{error, Reason} -> {error, {pre_config_update, HandlerName, Reason}} {error, Reason} -> {error, {pre_config_update, HandlerName, Reason}}
end; end;
false -> merge_to_old_config(UpdateReq, OldRawConf) false ->
merge_to_old_config(UpdateReq, OldRawConf)
end; end;
call_pre_config_update(_Handlers, OldRawConf, UpdateReq, _ConfKeyPath) -> call_pre_config_update(_Handlers, OldRawConf, UpdateReq, _ConfKeyPath) ->
merge_to_old_config(UpdateReq, OldRawConf). merge_to_old_config(UpdateReq, OldRawConf).
call_post_config_update(#{?MOD := HandlerName}, OldConf, NewConf, call_post_config_update(
AppEnvs, UpdateReq, Result, ConfKeyPath) -> #{?MOD := HandlerName},
OldConf,
NewConf,
AppEnvs,
UpdateReq,
Result,
ConfKeyPath
) ->
case erlang:function_exported(HandlerName, post_config_update, 5) of case erlang:function_exported(HandlerName, post_config_update, 5) of
true -> true ->
case HandlerName:post_config_update(ConfKeyPath, UpdateReq, case
NewConf, OldConf, AppEnvs) of HandlerName:post_config_update(
ConfKeyPath,
UpdateReq,
NewConf,
OldConf,
AppEnvs
)
of
ok -> {ok, Result}; ok -> {ok, Result};
{ok, Result1} -> {ok, Result#{HandlerName => Result1}}; {ok, Result1} -> {ok, Result#{HandlerName => Result1}};
{error, Reason} -> {error, {post_config_update, HandlerName, Reason}} {error, Reason} -> {error, {post_config_update, HandlerName, Reason}}
end; end;
false -> {ok, Result} false ->
{ok, Result}
end; end;
call_post_config_update(_Handlers, _OldConf, _NewConf, _AppEnvs, call_post_config_update(
_UpdateReq, Result, _ConfKeyPath) -> _Handlers,
_OldConf,
_NewConf,
_AppEnvs,
_UpdateReq,
Result,
_ConfKeyPath
) ->
{ok, Result}. {ok, Result}.
save_configs(ConfKeyPath, AppEnvs, CheckedConf, NewRawConf, OverrideConf, UpdateArgs, Opts) -> save_configs(ConfKeyPath, AppEnvs, CheckedConf, NewRawConf, OverrideConf, UpdateArgs, Opts) ->
@ -350,8 +470,10 @@ up_req({remove, _Opts}) -> '$remove';
up_req({{update, Req}, _Opts}) -> Req. up_req({{update, Req}, _Opts}) -> Req.
return_change_result(ConfKeyPath, {{update, _Req}, Opts}) -> return_change_result(ConfKeyPath, {{update, _Req}, Opts}) ->
#{config => emqx_config:get(ConfKeyPath), #{
raw_config => return_rawconf(ConfKeyPath, Opts)}; config => emqx_config:get(ConfKeyPath),
raw_config => return_rawconf(ConfKeyPath, Opts)
};
return_change_result(_ConfKeyPath, {remove, _Opts}) -> return_change_result(_ConfKeyPath, {remove, _Opts}) ->
#{}. #{}.
@ -378,19 +500,24 @@ do_remove_handler(ConfKeyPath, Handlers) ->
NewHandlers = emqx_map_lib:deep_remove(ConfKeyPath ++ [?MOD], Handlers), NewHandlers = emqx_map_lib:deep_remove(ConfKeyPath ++ [?MOD], Handlers),
remove_empty_leaf(ConfKeyPath, NewHandlers). remove_empty_leaf(ConfKeyPath, NewHandlers).
remove_empty_leaf([], Handlers) -> Handlers; remove_empty_leaf([], Handlers) ->
Handlers;
remove_empty_leaf(KeyPath, Handlers) -> remove_empty_leaf(KeyPath, Handlers) ->
case emqx_map_lib:deep_find(KeyPath, Handlers) =:= {ok, #{}} of case emqx_map_lib:deep_find(KeyPath, Handlers) =:= {ok, #{}} of
true -> %% empty leaf %% empty leaf
true ->
Handlers1 = emqx_map_lib:deep_remove(KeyPath, Handlers), Handlers1 = emqx_map_lib:deep_remove(KeyPath, Handlers),
SubKeyPath = lists:sublist(KeyPath, length(KeyPath) - 1), SubKeyPath = lists:sublist(KeyPath, length(KeyPath) - 1),
remove_empty_leaf(SubKeyPath, Handlers1); remove_empty_leaf(SubKeyPath, Handlers1);
false -> Handlers false ->
Handlers
end. end.
assert_callback_function(Mod) -> assert_callback_function(Mod) ->
case erlang:function_exported(Mod, pre_config_update, 3) orelse case
erlang:function_exported(Mod, post_config_update, 5) of erlang:function_exported(Mod, pre_config_update, 3) orelse
erlang:function_exported(Mod, post_config_update, 5)
of
true -> ok; true -> ok;
false -> error(#{msg => "bad_emqx_config_handler_callback", module => Mod}) false -> error(#{msg => "bad_emqx_config_handler_callback", module => Mod})
end, end,
@ -410,12 +537,16 @@ schema(SchemaModule, [RootKey | _]) ->
validations => hocon_schema:validations(SchemaModule) validations => hocon_schema:validations(SchemaModule)
}. }.
parse_translations(#{translate_to := TRs } = Field, Name, SchemaModule) -> parse_translations(#{translate_to := TRs} = Field, Name, SchemaModule) ->
{ {
{Name, maps:remove(translate_to, Field)}, {Name, maps:remove(translate_to, Field)},
lists:foldl(fun(T, Acc) -> lists:foldl(
Acc#{T => hocon_schema:translation(SchemaModule, T)} fun(T, Acc) ->
end, #{}, TRs) Acc#{T => hocon_schema:translation(SchemaModule, T)}
end,
#{},
TRs
)
}; };
parse_translations(Field, Name, _SchemaModule) -> parse_translations(Field, Name, _SchemaModule) ->
{{Name, Field}, #{}}. {{Name, Field}, #{}}.

View File

@ -16,21 +16,41 @@
-module(emqx_congestion). -module(emqx_congestion).
-export([ maybe_alarm_conn_congestion/3 -export([
, cancel_alarms/3 maybe_alarm_conn_congestion/3,
]). cancel_alarms/3
]).
-elvis([{elvis_style, invalid_dynamic_call, #{ignore => [emqx_congestion]}}]). -elvis([{elvis_style, invalid_dynamic_call, #{ignore => [emqx_congestion]}}]).
-define(ALARM_CONN_CONGEST(Channel, Reason), -define(ALARM_CONN_CONGEST(Channel, Reason),
list_to_binary( list_to_binary(
io_lib:format("~ts/~ts/~ts", io_lib:format(
[Reason, emqx_channel:info(clientid, Channel), "~ts/~ts/~ts",
maps:get(username, emqx_channel:info(clientinfo, Channel), [
<<"unknown_user">>)]))). Reason,
emqx_channel:info(clientid, Channel),
maps:get(
username,
emqx_channel:info(clientinfo, Channel),
<<"unknown_user">>
)
]
)
)
).
-define(ALARM_CONN_INFO_KEYS, [socktype, sockname, peername, clientid, username, -define(ALARM_CONN_INFO_KEYS, [
proto_name, proto_ver, connected_at, conn_state]). socktype,
sockname,
peername,
clientid,
username,
proto_name,
proto_ver,
connected_at,
conn_state
]).
-define(ALARM_SOCK_STATS_KEYS, [send_pend, recv_cnt, recv_oct, send_cnt, send_oct]). -define(ALARM_SOCK_STATS_KEYS, [send_pend, recv_cnt, recv_oct, send_cnt, send_oct]).
-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]).
@ -40,7 +60,8 @@
maybe_alarm_conn_congestion(Socket, Transport, Channel) -> maybe_alarm_conn_congestion(Socket, Transport, Channel) ->
case is_alarm_enabled(Channel) of case is_alarm_enabled(Channel) of
false -> ok; false ->
ok;
true -> true ->
case is_tcp_congested(Socket, Transport) of case is_tcp_congested(Socket, Transport) of
true -> alarm_congestion(Socket, Transport, Channel, conn_congestion); true -> alarm_congestion(Socket, Transport, Channel, conn_congestion);
@ -49,12 +70,15 @@ maybe_alarm_conn_congestion(Socket, Transport, Channel) ->
end. end.
cancel_alarms(Socket, Transport, Channel) -> cancel_alarms(Socket, Transport, Channel) ->
lists:foreach(fun(Reason) -> lists:foreach(
case has_alarm_sent(Reason) of fun(Reason) ->
true -> do_cancel_alarm_congestion(Socket, Transport, Channel, Reason); case has_alarm_sent(Reason) of
false -> ok true -> do_cancel_alarm_congestion(Socket, Transport, Channel, Reason);
end false -> ok
end, ?ALL_ALARM_REASONS). end
end,
?ALL_ALARM_REASONS
).
is_alarm_enabled(Channel) -> is_alarm_enabled(Channel) ->
Zone = emqx_channel:info(zone, Channel), Zone = emqx_channel:info(zone, Channel),
@ -62,7 +86,8 @@ is_alarm_enabled(Channel) ->
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);
true -> true ->
%% pretend we have sent an alarm again %% pretend we have sent an alarm again
update_alarm_sent_at(Reason) update_alarm_sent_at(Reason)
@ -70,8 +95,10 @@ alarm_congestion(Socket, Transport, Channel, Reason) ->
cancel_alarm_congestion(Socket, Transport, Channel, Reason) -> cancel_alarm_congestion(Socket, Transport, Channel, Reason) ->
Zone = emqx_channel:info(zone, Channel), Zone = emqx_channel:info(zone, Channel),
WontClearIn = emqx_config:get_zone_conf(Zone, [conn_congestion, WontClearIn = emqx_config:get_zone_conf(Zone, [
min_alarm_sustain_duration]), conn_congestion,
min_alarm_sustain_duration
]),
case has_alarm_sent(Reason) andalso long_time_since_last_alarm(Reason, WontClearIn) of 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
@ -130,14 +157,16 @@ timenow() ->
tcp_congestion_alarm_details(Socket, Transport, Channel) -> tcp_congestion_alarm_details(Socket, Transport, Channel) ->
ProcInfo = process_info(self(), ?PROC_INFO_KEYS), ProcInfo = process_info(self(), ?PROC_INFO_KEYS),
BasicInfo = [{pid, list_to_binary(pid_to_list(self()))} | ProcInfo], BasicInfo = [{pid, list_to_binary(pid_to_list(self()))} | ProcInfo],
Stat = case Transport:getstat(Socket, ?ALARM_SOCK_STATS_KEYS) of Stat =
{ok, Stat0} -> Stat0; case Transport:getstat(Socket, ?ALARM_SOCK_STATS_KEYS) of
{error, _} -> [] {ok, Stat0} -> Stat0;
end, {error, _} -> []
Opts = case Transport:getopts(Socket, ?ALARM_SOCK_OPTS_KEYS) of end,
{ok, Opts0} -> Opts0; Opts =
{error, _} -> [] case Transport:getopts(Socket, ?ALARM_SOCK_OPTS_KEYS) of
end, {ok, Opts0} -> Opts0;
{error, _} -> []
end,
SockInfo = Stat ++ Opts, SockInfo = Stat ++ Opts,
ConnInfo = [conn_info(Key, Channel) || Key <- ?ALARM_CONN_INFO_KEYS], ConnInfo = [conn_info(Key, Channel) || Key <- ?ALARM_CONN_INFO_KEYS],
maps:from_list(BasicInfo ++ ConnInfo ++ SockInfo). maps:from_list(BasicInfo ++ ConnInfo ++ SockInfo).

File diff suppressed because it is too large Load Diff

View File

@ -21,67 +21,71 @@
-include("types.hrl"). -include("types.hrl").
-include("logger.hrl"). -include("logger.hrl").
-export([start_link/0, stop/0]). -export([start_link/0, stop/0]).
-export([ register_command/2 -export([
, register_command/3 register_command/2,
, unregister_command/1 register_command/3,
]). unregister_command/1
]).
-export([ run_command/1 -export([
, run_command/2 run_command/1,
, lookup_command/1 run_command/2,
, get_commands/0 lookup_command/1,
]). get_commands/0
]).
-export([ print/1 -export([
, print/2 print/1,
, usage/1 print/2,
, usage/2 usage/1,
]). usage/2
]).
%% Exports mainly for test cases %% Exports mainly for test cases
-export([ format/2 -export([
, format_usage/1 format/2,
, format_usage/2 format_usage/1,
]). format_usage/2
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-record(state, {seq = 0}). -record(state, {seq = 0}).
-type(cmd() :: atom()). -type cmd() :: atom().
-type(cmd_params() :: string()). -type cmd_params() :: string().
-type(cmd_descr() :: string()). -type cmd_descr() :: string().
-type(cmd_usage() :: {cmd_params(), cmd_descr()}). -type cmd_usage() :: {cmd_params(), cmd_descr()}.
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-define(CMD_TAB, emqx_command). -define(CMD_TAB, emqx_command).
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(stop() -> ok). -spec stop() -> ok.
stop() -> gen_server:stop(?SERVER). stop() -> gen_server:stop(?SERVER).
-spec(register_command(cmd(), {module(), atom()}) -> ok). -spec register_command(cmd(), {module(), atom()}) -> ok.
register_command(Cmd, MF) when is_atom(Cmd) -> register_command(Cmd, MF) when is_atom(Cmd) ->
register_command(Cmd, MF, []). register_command(Cmd, MF, []).
-spec(register_command(cmd(), {module(), atom()}, list()) -> ok). -spec register_command(cmd(), {module(), atom()}, list()) -> ok.
register_command(Cmd, MF, Opts) when is_atom(Cmd) -> register_command(Cmd, MF, Opts) when is_atom(Cmd) ->
call({register_command, Cmd, MF, Opts}). call({register_command, Cmd, MF, Opts}).
-spec(unregister_command(cmd()) -> ok). -spec unregister_command(cmd()) -> ok.
unregister_command(Cmd) when is_atom(Cmd) -> unregister_command(Cmd) when is_atom(Cmd) ->
cast({unregister_command, Cmd}). cast({unregister_command, Cmd}).
@ -89,14 +93,15 @@ call(Req) -> gen_server:call(?SERVER, Req).
cast(Msg) -> gen_server:cast(?SERVER, Msg). cast(Msg) -> gen_server:cast(?SERVER, Msg).
-spec(run_command(list(string())) -> ok | {error, term()}). -spec run_command(list(string())) -> ok | {error, term()}.
run_command([]) -> run_command([]) ->
run_command(help, []); run_command(help, []);
run_command([Cmd | Args]) -> run_command([Cmd | Args]) ->
run_command(list_to_atom(Cmd), Args). run_command(list_to_atom(Cmd), Args).
-spec(run_command(cmd(), list(string())) -> ok | {error, term()}). -spec run_command(cmd(), list(string())) -> ok | {error, term()}.
run_command(help, []) -> help(); run_command(help, []) ->
help();
run_command(Cmd, Args) when is_atom(Cmd) -> run_command(Cmd, Args) when is_atom(Cmd) ->
case lookup_command(Cmd) of case lookup_command(Cmd) of
[{Mod, Fun}] -> [{Mod, Fun}] ->
@ -104,24 +109,26 @@ run_command(Cmd, Args) when is_atom(Cmd) ->
_ -> ok _ -> ok
catch catch
_:Reason:Stacktrace -> _:Reason:Stacktrace ->
?SLOG(error, #{msg => "ctl_command_crashed", ?SLOG(error, #{
stacktrace => Stacktrace, msg => "ctl_command_crashed",
reason => Reason stacktrace => Stacktrace,
}), reason => Reason
}),
{error, Reason} {error, Reason}
end; end;
[] -> [] ->
help(), {error, cmd_not_found} help(),
{error, cmd_not_found}
end. end.
-spec(lookup_command(cmd()) -> [{module(), atom()}]). -spec lookup_command(cmd()) -> [{module(), atom()}].
lookup_command(Cmd) when is_atom(Cmd) -> lookup_command(Cmd) when is_atom(Cmd) ->
case ets:match(?CMD_TAB, {{'_', Cmd}, '$1', '_'}) of case ets:match(?CMD_TAB, {{'_', Cmd}, '$1', '_'}) of
[El] -> El; [El] -> El;
[] -> [] [] -> []
end. end.
-spec(get_commands() -> list({cmd(), module(), atom()})). -spec get_commands() -> list({cmd(), module(), atom()}).
get_commands() -> get_commands() ->
[{Cmd, M, F} || {{_Seq, Cmd}, {M, F}, _Opts} <- ets:tab2list(?CMD_TAB)]. [{Cmd, M, F} || {{_Seq, Cmd}, {M, F}, _Opts} <- ets:tab2list(?CMD_TAB)].
@ -131,42 +138,52 @@ help() ->
print("No commands available.~n"); print("No commands available.~n");
Cmds -> Cmds ->
print("Usage: ~ts~n", [?MODULE]), print("Usage: ~ts~n", [?MODULE]),
lists:foreach(fun({_, {Mod, Cmd}, _}) -> lists:foreach(
print("~110..-s~n", [""]), Mod:Cmd(usage) fun({_, {Mod, Cmd}, _}) ->
end, Cmds) print("~110..-s~n", [""]),
Mod:Cmd(usage)
end,
Cmds
)
end. end.
-spec(print(io:format()) -> ok). -spec print(io:format()) -> ok.
print(Msg) -> print(Msg) ->
io:format("~ts", [format(Msg, [])]). io:format("~ts", [format(Msg, [])]).
-spec(print(io:format(), [term()]) -> ok). -spec print(io:format(), [term()]) -> ok.
print(Format, Args) -> print(Format, Args) ->
io:format("~ts", [format(Format, Args)]). io:format("~ts", [format(Format, Args)]).
-spec(usage([cmd_usage()]) -> ok). -spec usage([cmd_usage()]) -> ok.
usage(UsageList) -> usage(UsageList) ->
io:format(format_usage(UsageList)). io:format(format_usage(UsageList)).
-spec(usage(cmd_params(), cmd_descr()) -> ok). -spec usage(cmd_params(), cmd_descr()) -> ok.
usage(CmdParams, Desc) -> usage(CmdParams, Desc) ->
io:format(format_usage(CmdParams, Desc)). io:format(format_usage(CmdParams, Desc)).
-spec(format(io:format(), [term()]) -> string()). -spec format(io:format(), [term()]) -> string().
format(Format, Args) -> format(Format, Args) ->
lists:flatten(io_lib:format(Format, Args)). lists:flatten(io_lib:format(Format, Args)).
-spec(format_usage([cmd_usage()]) -> [string()]). -spec format_usage([cmd_usage()]) -> [string()].
format_usage(UsageList) -> format_usage(UsageList) ->
Width = lists:foldl(fun({CmdStr, _}, W) -> Width = lists:foldl(
max(iolist_size(CmdStr), W) fun({CmdStr, _}, W) ->
end, 0, UsageList), max(iolist_size(CmdStr), W)
end,
0,
UsageList
),
lists:map( lists:map(
fun({CmdParams, Desc}) -> fun({CmdParams, Desc}) ->
format_usage(CmdParams, Desc, Width) format_usage(CmdParams, Desc, Width)
end, UsageList). end,
UsageList
).
-spec(format_usage(cmd_params(), cmd_descr()) -> string()). -spec format_usage(cmd_params(), cmd_descr()) -> string().
format_usage(CmdParams, Desc) -> format_usage(CmdParams, Desc) ->
format_usage(CmdParams, Desc, 0). format_usage(CmdParams, Desc, 0).
@ -177,9 +194,13 @@ format_usage(CmdParams, Desc, Width) ->
DescLines = split_cmd(Desc), DescLines = split_cmd(Desc),
Zipped = zip_cmd(CmdLines, DescLines), Zipped = zip_cmd(CmdLines, DescLines),
Fmt = "~-" ++ integer_to_list(Width + 1) ++ "s# ~ts~n", Fmt = "~-" ++ integer_to_list(Width + 1) ++ "s# ~ts~n",
lists:foldl(fun({CmdStr, DescStr}, Usage) -> lists:foldl(
Usage ++ format(Fmt, [CmdStr, DescStr]) fun({CmdStr, DescStr}, Usage) ->
end, "", Zipped). Usage ++ format(Fmt, [CmdStr, DescStr])
end,
"",
Zipped
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
@ -191,13 +212,13 @@ init([]) ->
handle_call({register_command, Cmd, MF, Opts}, _From, State = #state{seq = Seq}) -> handle_call({register_command, Cmd, MF, Opts}, _From, State = #state{seq = Seq}) ->
case ets:match(?CMD_TAB, {{'$1', Cmd}, '_', '_'}) of case ets:match(?CMD_TAB, {{'$1', Cmd}, '_', '_'}) of
[] -> ets:insert(?CMD_TAB, {{Seq, Cmd}, MF, Opts}); [] ->
ets:insert(?CMD_TAB, {{Seq, Cmd}, MF, Opts});
[[OriginSeq] | _] -> [[OriginSeq] | _] ->
?SLOG(warning, #{msg => "CMD_overidden", cmd => Cmd, mf => MF}), ?SLOG(warning, #{msg => "CMD_overidden", cmd => Cmd, mf => MF}),
true = ets:insert(?CMD_TAB, {{OriginSeq, Cmd}, MF, Opts}) true = ets:insert(?CMD_TAB, {{OriginSeq, Cmd}, MF, Opts})
end, end,
{reply, ok, next_seq(State)}; {reply, ok, next_seq(State)};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -205,7 +226,6 @@ handle_call(Req, _From, State) ->
handle_cast({unregister_command, Cmd}, State) -> handle_cast({unregister_command, Cmd}, State) ->
ets:match_delete(?CMD_TAB, {{'_', Cmd}, '_', '_'}), ets:match_delete(?CMD_TAB, {{'_', Cmd}, '_', '_'}),
noreply(State); noreply(State);
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
noreply(State). noreply(State).

View File

@ -18,16 +18,19 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
%% API %% API
-export([ to_epoch_millisecond/1 -export([
, to_epoch_second/1 to_epoch_millisecond/1,
]). to_epoch_second/1
-export([ epoch_to_rfc3339/1 ]).
, epoch_to_rfc3339/2 -export([
]). epoch_to_rfc3339/1,
epoch_to_rfc3339/2
]).
-reflect_type([ epoch_millisecond/0 -reflect_type([
, epoch_second/0 epoch_millisecond/0,
]). epoch_second/0
]).
-type epoch_second() :: non_neg_integer(). -type epoch_second() :: non_neg_integer().
-type epoch_millisecond() :: non_neg_integer(). -type epoch_millisecond() :: non_neg_integer().
@ -47,8 +50,9 @@ to_epoch(DateTime, Unit) ->
{_Epoch, []} -> {error, bad_epoch}; {_Epoch, []} -> {error, bad_epoch};
_ -> {ok, calendar:rfc3339_to_system_time(DateTime, [{unit, Unit}])} _ -> {ok, calendar:rfc3339_to_system_time(DateTime, [{unit, Unit}])}
end end
catch error: _ -> catch
{error, bad_rfc3339_timestamp} error:_ ->
{error, bad_rfc3339_timestamp}
end. end.
epoch_to_rfc3339(TimeStamp) -> epoch_to_rfc3339(TimeStamp) ->
@ -69,8 +73,11 @@ fields(bar) ->
{millisecond, ?MODULE:epoch_millisecond()} {millisecond, ?MODULE:epoch_millisecond()}
]. ].
-define(FORMAT(_Sec_, _Ms_), lists:flatten( -define(FORMAT(_Sec_, _Ms_),
io_lib:format("bar={second=~w,millisecond=~w}", [_Sec_, _Ms_]))). lists:flatten(
io_lib:format("bar={second=~w,millisecond=~w}", [_Sec_, _Ms_])
)
).
epoch_ok_test() -> epoch_ok_test() ->
Args = [ Args = [
@ -78,31 +85,43 @@ epoch_ok_test() ->
{1, 1, 1, 1}, {1, 1, 1, 1},
{"2022-01-01T08:00:00+08:00", "2022-01-01T08:00:00+08:00", 1640995200, 1640995200000} {"2022-01-01T08:00:00+08:00", "2022-01-01T08:00:00+08:00", 1640995200, 1640995200000}
], ],
lists:foreach(fun({Sec, Ms, EpochSec, EpochMs}) -> lists:foreach(
check_ok(?FORMAT(Sec, Ms), EpochSec, EpochMs) fun({Sec, Ms, EpochSec, EpochMs}) ->
end, Args), check_ok(?FORMAT(Sec, Ms), EpochSec, EpochMs)
end,
Args
),
ok. ok.
check_ok(Input, Sec, Ms) -> check_ok(Input, Sec, Ms) ->
{ok, Data} = hocon:binary(Input, #{}), {ok, Data} = hocon:binary(Input, #{}),
?assertMatch(#{bar := #{second := Sec, millisecond := Ms}}, ?assertMatch(
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])), #{bar := #{second := Sec, millisecond := Ms}},
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])
),
ok. ok.
epoch_failed_test() -> epoch_failed_test() ->
Args = [ Args = [
{-1, -1}, {-1, -1},
{"1s", "1s"}, {"1s", "1s"},
{"2022-13-13T08:00:00+08:00", "2022-13-13T08:00:00+08:00"}], {"2022-13-13T08:00:00+08:00", "2022-13-13T08:00:00+08:00"}
lists:foreach(fun({Sec, Ms}) -> ],
check_failed(?FORMAT(Sec, Ms)) lists:foreach(
end, Args), fun({Sec, Ms}) ->
check_failed(?FORMAT(Sec, Ms))
end,
Args
),
ok. ok.
check_failed(Input) -> check_failed(Input) ->
{ok, Data} = hocon:binary(Input, #{}), {ok, Data} = hocon:binary(Input, #{}),
?assertException(throw, _, ?assertException(
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])), throw,
_,
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])
),
ok. ok.
-endif. -endif.

View File

@ -28,13 +28,14 @@
-export([detect/1]). -export([detect/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
%% Tab %% Tab
-define(FLAPPING_TAB, ?MODULE). -define(FLAPPING_TAB, ?MODULE).
@ -42,31 +43,31 @@
-define(FLAPPING_THRESHOLD, 30). -define(FLAPPING_THRESHOLD, 30).
-define(FLAPPING_DURATION, 60000). -define(FLAPPING_DURATION, 60000).
-define(FLAPPING_BANNED_INTERVAL, 300000). -define(FLAPPING_BANNED_INTERVAL, 300000).
-define(DEFAULT_DETECT_POLICY, -define(DEFAULT_DETECT_POLICY, #{
#{max_count => ?FLAPPING_THRESHOLD, max_count => ?FLAPPING_THRESHOLD,
window_time => ?FLAPPING_DURATION, window_time => ?FLAPPING_DURATION,
ban_time => ?FLAPPING_BANNED_INTERVAL ban_time => ?FLAPPING_BANNED_INTERVAL
}). }).
-record(flapping, { -record(flapping, {
clientid :: emqx_types:clientid(), clientid :: emqx_types:clientid(),
peerhost :: emqx_types:peerhost(), peerhost :: emqx_types:peerhost(),
started_at :: pos_integer(), started_at :: pos_integer(),
detect_cnt :: integer() detect_cnt :: integer()
}). }).
-opaque(flapping() :: #flapping{}). -opaque flapping() :: #flapping{}.
-export_type([flapping/0]). -export_type([flapping/0]).
-spec(start_link() -> emqx_types:startlink_ret()). -spec start_link() -> emqx_types:startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() -> gen_server:stop(?MODULE). stop() -> gen_server:stop(?MODULE).
%% @doc Detect flapping when a MQTT client disconnected. %% @doc Detect flapping when a MQTT client disconnected.
-spec(detect(emqx_types:clientinfo()) -> boolean()). -spec detect(emqx_types:clientinfo()) -> boolean().
detect(#{clientid := ClientId, peerhost := PeerHost, zone := Zone}) -> detect(#{clientid := ClientId, peerhost := PeerHost, zone := Zone}) ->
Policy = #{max_count := Threshold} = get_policy(Zone), Policy = #{max_count := Threshold} = get_policy(Zone),
%% The initial flapping record sets the detect_cnt to 0. %% The initial flapping record sets the detect_cnt to 0.
@ -83,7 +84,8 @@ detect(#{clientid := ClientId, peerhost := PeerHost, zone := Zone}) ->
[Flapping] -> [Flapping] ->
ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}), ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}),
true; true;
[] -> false [] ->
false
end end
end. end.
@ -97,11 +99,13 @@ now_diff(TS) -> erlang:system_time(millisecond) - TS.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
ok = emqx_tables:new(?FLAPPING_TAB, [public, set, ok = emqx_tables:new(?FLAPPING_TAB, [
{keypos, #flapping.clientid}, public,
{read_concurrency, true}, set,
{write_concurrency, true} {keypos, #flapping.clientid},
]), {read_concurrency, true},
{write_concurrency, true}
]),
start_timers(), start_timers(),
{ok, #{}, hibernate}. {ok, #{}, hibernate}.
@ -109,49 +113,65 @@ handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.
handle_cast({detected, #flapping{clientid = ClientId, handle_cast(
peerhost = PeerHost, {detected,
started_at = StartedAt, #flapping{
detect_cnt = DetectCnt}, clientid = ClientId,
#{window_time := WindTime, ban_time := Interval}}, State) -> peerhost = PeerHost,
started_at = StartedAt,
detect_cnt = DetectCnt
},
#{window_time := WindTime, ban_time := Interval}},
State
) ->
case now_diff(StartedAt) < WindTime of case now_diff(StartedAt) < WindTime of
true -> %% Flapping happened:( %% Flapping happened:(
?SLOG(warning, #{ true ->
msg => "flapping_detected", ?SLOG(
peer_host => fmt_host(PeerHost), warning,
detect_cnt => DetectCnt, #{
wind_time_in_ms => WindTime msg => "flapping_detected",
}, #{clientid => ClientId}), peer_host => fmt_host(PeerHost),
detect_cnt => DetectCnt,
wind_time_in_ms => WindTime
},
#{clientid => ClientId}
),
Now = erlang:system_time(second), Now = erlang:system_time(second),
Banned = #banned{who = {clientid, ClientId}, Banned = #banned{
by = <<"flapping detector">>, who = {clientid, ClientId},
reason = <<"flapping is detected">>, by = <<"flapping detector">>,
at = Now, reason = <<"flapping is detected">>,
until = Now + (Interval div 1000)}, at = Now,
until = Now + (Interval div 1000)
},
{ok, _} = emqx_banned:create(Banned), {ok, _} = emqx_banned:create(Banned),
ok; ok;
false -> false ->
?SLOG(warning, #{ ?SLOG(
msg => "client_disconnected", warning,
peer_host => fmt_host(PeerHost), #{
detect_cnt => DetectCnt, msg => "client_disconnected",
interval => Interval peer_host => fmt_host(PeerHost),
}, #{clientid => ClientId}) detect_cnt => DetectCnt,
interval => Interval
},
#{clientid => ClientId}
)
end, end,
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
handle_info({timeout, _TRef, {garbage_collect, Zone}}, State) -> handle_info({timeout, _TRef, {garbage_collect, Zone}}, State) ->
Timestamp = erlang:system_time(millisecond) Timestamp =
- maps:get(window_time, get_policy(Zone)), erlang:system_time(millisecond) -
MatchSpec = [{{'_', '_', '_', '$1', '_'},[{'<', '$1', Timestamp}], [true]}], maps:get(window_time, get_policy(Zone)),
MatchSpec = [{{'_', '_', '_', '$1', '_'}, [{'<', '$1', Timestamp}], [true]}],
ets:select_delete(?FLAPPING_TAB, MatchSpec), ets:select_delete(?FLAPPING_TAB, MatchSpec),
_ = start_timer(Zone), _ = start_timer(Zone),
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -167,11 +187,16 @@ start_timer(Zone) ->
emqx_misc:start_timer(WindTime, {garbage_collect, Zone}). emqx_misc:start_timer(WindTime, {garbage_collect, Zone}).
start_timers() -> start_timers() ->
lists:foreach(fun({Zone, _ZoneConf}) -> lists:foreach(
fun({Zone, _ZoneConf}) ->
start_timer(Zone) start_timer(Zone)
end, maps:to_list(emqx:get_config([zones], #{}))). end,
maps:to_list(emqx:get_config([zones], #{}))
).
fmt_host(PeerHost) -> fmt_host(PeerHost) ->
try inet:ntoa(PeerHost) try
catch _:_ -> PeerHost inet:ntoa(PeerHost)
catch
_:_ -> PeerHost
end. end.

File diff suppressed because it is too large Load Diff

View File

@ -28,22 +28,27 @@
-include("types.hrl"). -include("types.hrl").
-export([ init/1 -export([
, run/2 init/1,
, run/3 run/2,
, info/1 run/3,
, reset/1 info/1,
]). reset/1
]).
-export_type([opts/0, gc_state/0]). -export_type([opts/0, gc_state/0]).
-type(opts() :: #{count => integer(), -type opts() :: #{
bytes => integer()}). count => integer(),
bytes => integer()
}.
-type(st() :: #{cnt => {integer(), integer()}, -type st() :: #{
oct => {integer(), integer()}}). cnt => {integer(), integer()},
oct => {integer(), integer()}
}.
-opaque(gc_state() :: {gc_state, st()}). -opaque gc_state() :: {gc_state, st()}.
-define(GCS(St), {gc_state, St}). -define(GCS(St), {gc_state, St}).
@ -51,27 +56,27 @@
-define(ENABLED(X), (is_integer(X) andalso X > 0)). -define(ENABLED(X), (is_integer(X) andalso X > 0)).
%% @doc Initialize force GC state. %% @doc Initialize force GC state.
-spec(init(opts()) -> gc_state()). -spec init(opts()) -> gc_state().
init(#{count := Count, bytes := Bytes}) -> init(#{count := Count, bytes := Bytes}) ->
Cnt = [{cnt, {Count, Count}} || ?ENABLED(Count)], Cnt = [{cnt, {Count, Count}} || ?ENABLED(Count)],
Oct = [{oct, {Bytes, Bytes}} || ?ENABLED(Bytes)], Oct = [{oct, {Bytes, Bytes}} || ?ENABLED(Bytes)],
?GCS(maps:from_list(Cnt ++ Oct)). ?GCS(maps:from_list(Cnt ++ Oct)).
%% @doc Try to run GC based on reduntions of count or bytes. %% @doc Try to run GC based on reduntions of count or bytes.
-spec(run(#{cnt := pos_integer(), oct := pos_integer()}, gc_state()) -spec run(#{cnt := pos_integer(), oct := pos_integer()}, gc_state()) ->
-> {boolean(), gc_state()}). {boolean(), gc_state()}.
run(#{cnt := Cnt, oct := Oct}, GcSt) -> run(#{cnt := Cnt, oct := Oct}, GcSt) ->
run(Cnt, Oct, GcSt). run(Cnt, Oct, GcSt).
-spec(run(pos_integer(), pos_integer(), gc_state()) -spec run(pos_integer(), pos_integer(), gc_state()) ->
-> {boolean(), gc_state()}). {boolean(), gc_state()}.
run(Cnt, Oct, ?GCS(St)) -> run(Cnt, Oct, ?GCS(St)) ->
{Res, St1} = do_run([{cnt, Cnt}, {oct, Oct}], St), {Res, St1} = do_run([{cnt, Cnt}, {oct, Oct}], St),
{Res, ?GCS(St1)}. {Res, ?GCS(St1)}.
do_run([], St) -> do_run([], St) ->
{false, St}; {false, St};
do_run([{K, N}|T], St) -> do_run([{K, N} | T], St) ->
case dec(K, N, St) of case dec(K, N, St) of
{true, St1} -> {true, St1} ->
erlang:garbage_collect(), erlang:garbage_collect(),
@ -81,11 +86,11 @@ do_run([{K, N}|T], St) ->
end. end.
%% @doc Info of GC state. %% @doc Info of GC state.
-spec(info(maybe(gc_state())) -> maybe(map())). -spec info(maybe(gc_state())) -> maybe(map()).
info(?GCS(St)) -> St. info(?GCS(St)) -> St.
%% @doc Reset counters to zero. %% @doc Reset counters to zero.
-spec(reset(maybe(gc_state())) -> gc_state()). -spec reset(maybe(gc_state())) -> gc_state().
reset(?GCS(St)) -> reset(?GCS(St)) ->
?GCS(do_reset(St)). ?GCS(do_reset(St)).
@ -93,7 +98,7 @@ reset(?GCS(St)) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(dec(cnt | oct, pos_integer(), st()) -> {boolean(), st()}). -spec dec(cnt | oct, pos_integer(), st()) -> {boolean(), st()}.
dec(Key, Num, St) -> dec(Key, Num, St) ->
case maps:get(Key, St, ?disabled) of case maps:get(Key, St, ?disabled) of
?disabled -> ?disabled ->
@ -113,4 +118,3 @@ do_reset(Key, St) ->
?disabled -> St; ?disabled -> St;
{Init, _} -> maps:put(Key, {Init, Init}, St) {Init, _} -> maps:put(Key, {Init, Init}, St)
end. end.

View File

@ -30,17 +30,17 @@
-module(emqx_guid). -module(emqx_guid).
-export([ gen/0 -export([
, new/0 gen/0,
, timestamp/1 new/0,
, to_hexstr/1 timestamp/1,
, from_hexstr/1 to_hexstr/1,
, to_base62/1 from_hexstr/1,
, from_base62/1 to_base62/1,
]). from_base62/1
]).
-export_type([ guid/0 -export_type([guid/0]).
]).
-define(TAG_VERSION, 131). -define(TAG_VERSION, 131).
-define(PID_EXT, 103). -define(PID_EXT, 103).
@ -48,21 +48,23 @@
-define(MAX_SEQ, 16#FFFF). -define(MAX_SEQ, 16#FFFF).
-type(guid() :: <<_:128>>). -type guid() :: <<_:128>>.
%% @doc Generate a global unique id. %% @doc Generate a global unique id.
-spec(gen() -> guid()). -spec gen() -> guid().
gen() -> gen() ->
Guid = case get(guid) of Guid =
undefined -> new(); case get(guid) of
{_Ts, NPid, Seq} -> next(NPid, Seq) undefined -> new();
end, {_Ts, NPid, Seq} -> next(NPid, Seq)
put(guid, Guid), bin(Guid). end,
put(guid, Guid),
bin(Guid).
new() -> new() ->
{ts(), npid(), 0}. {ts(), npid(), 0}.
-spec(timestamp(guid()) -> integer()). -spec timestamp(guid()) -> integer().
timestamp(<<Ts:64, _/binary>>) -> timestamp(<<Ts:64, _/binary>>) ->
Ts. Ts.
@ -78,11 +80,10 @@ ts() -> erlang:system_time(micro_seconds).
%% Copied from https://github.com/okeuday/uuid.git. %% Copied from https://github.com/okeuday/uuid.git.
npid() -> npid() ->
<<NodeD01, NodeD02, NodeD03, NodeD04, NodeD05, <<NodeD01, NodeD02, NodeD03, NodeD04, NodeD05, NodeD06, NodeD07, NodeD08, NodeD09, NodeD10,
NodeD06, NodeD07, NodeD08, NodeD09, NodeD10, NodeD11, NodeD12, NodeD13, NodeD14, NodeD15, NodeD16, NodeD17, NodeD18, NodeD19,
NodeD11, NodeD12, NodeD13, NodeD14, NodeD15, NodeD20>> =
NodeD16, NodeD17, NodeD18, NodeD19, NodeD20>> = crypto:hash(sha, erlang:list_to_binary(erlang:atom_to_list(node()))),
crypto:hash(sha, erlang:list_to_binary(erlang:atom_to_list(node()))),
PidBin = PidBin =
case erlang:term_to_binary(self()) of case erlang:term_to_binary(self()) of
@ -95,38 +96,44 @@ npid() ->
end, end,
% 72/86 bits for the Erlang pid % 72/86 bits for the Erlang pid
<<PidID1:8, PidID2:8, PidID3:8, PidID4:8, % ID (Node specific, 15 bits)
PidSR1:8, PidSR2:8, PidSR3:8, PidSR4:8, % Serial (extra uniqueness)
PidCreation/binary % Node Creation Count
>> = PidBin,
PidCR1 = case PidCreation of % ID (Node specific, 15 bits)
<<D1>> -> <<PidID1:8, PidID2:8, PidID3:8, PidID4:8,
D1; % Serial (extra uniqueness)
<<D1, D2, D3, D4>> -> PidSR1:8, PidSR2:8, PidSR3:8, PidSR4:8,
D1 bxor D2 bxor D3 bxor D4 % Node Creation Count
end, PidCreation/binary>> = PidBin,
PidCR1 =
case PidCreation of
<<D1>> ->
D1;
<<D1, D2, D3, D4>> ->
D1 bxor D2 bxor D3 bxor D4
end,
% reduce the 160 bit NodeData checksum to 16 bits % reduce the 160 bit NodeData checksum to 16 bits
NodeByte1 = ((((((((NodeD01 bxor NodeD02) NodeByte1 =
bxor NodeD03) ((((((((NodeD01 bxor NodeD02) bxor
bxor NodeD04) NodeD03) bxor
bxor NodeD05) NodeD04) bxor
bxor NodeD06) NodeD05) bxor
bxor NodeD07) NodeD06) bxor
bxor NodeD08) NodeD07) bxor
bxor NodeD09) NodeD08) bxor
bxor NodeD10, NodeD09) bxor
NodeByte2 = (((((((((NodeD11 bxor NodeD12) NodeD10,
bxor NodeD13) NodeByte2 =
bxor NodeD14) (((((((((NodeD11 bxor NodeD12) bxor
bxor NodeD15) NodeD13) bxor
bxor NodeD16) NodeD14) bxor
bxor NodeD17) NodeD15) bxor
bxor NodeD18) NodeD16) bxor
bxor NodeD19) NodeD17) bxor
bxor NodeD20) NodeD18) bxor
bxor PidCR1, NodeD19) bxor
NodeD20) bxor
PidCR1,
% reduce the Erlang pid to 32 bits % reduce the Erlang pid to 32 bits
PidByte1 = PidID1 bxor PidSR4, PidByte1 = PidID1 bxor PidSR4,
@ -134,9 +141,7 @@ npid() ->
PidByte3 = PidID3 bxor PidSR2, PidByte3 = PidID3 bxor PidSR2,
PidByte4 = PidID4 bxor PidSR1, PidByte4 = PidID4 bxor PidSR1,
<<NPid:48>> = <<NodeByte1:8, NodeByte2:8, <<NPid:48>> = <<NodeByte1:8, NodeByte2:8, PidByte1:8, PidByte2:8, PidByte3:8, PidByte4:8>>,
PidByte1:8, PidByte2:8,
PidByte3:8, PidByte4:8>>,
NPid. NPid.
to_hexstr(I) when byte_size(I) =:= 16 -> to_hexstr(I) when byte_size(I) =:= 16 ->
@ -149,5 +154,5 @@ to_base62(<<I:128>>) ->
emqx_base62:encode(I). emqx_base62:encode(I).
from_base62(S) -> from_base62(S) ->
I = binary_to_integer( emqx_base62:decode(S)), I = binary_to_integer(emqx_base62:decode(S)),
<<I:128>>. <<I:128>>.

View File

@ -17,22 +17,22 @@
%% @doc HOCON schema help module %% @doc HOCON schema help module
-module(emqx_hocon). -module(emqx_hocon).
-export([ format_path/1 -export([
, check/2 format_path/1,
]). check/2
]).
%% @doc Format hocon config field path to dot-separated string in iolist format. %% @doc Format hocon config field path to dot-separated string in iolist format.
-spec format_path([atom() | string() | binary()]) -> iolist(). -spec format_path([atom() | string() | binary()]) -> iolist().
format_path([]) -> ""; format_path([]) -> "";
format_path([Name]) -> iol(Name); format_path([Name]) -> iol(Name);
format_path([Name | Rest]) -> format_path([Name | Rest]) -> [iol(Name), "." | format_path(Rest)].
[iol(Name) , "." | format_path(Rest)].
%% @doc Plain check the input config. %% @doc Plain check the input config.
%% The input can either be `richmap' or plain `map'. %% The input can either be `richmap' or plain `map'.
%% Always return plain map with atom keys. %% Always return plain map with atom keys.
-spec check(module(), hocon:config() | iodata()) -> -spec check(module(), hocon:config() | iodata()) ->
{ok, hocon:config()} | {error, any()}. {ok, hocon:config()} | {error, any()}.
check(SchemaModule, Conf) when is_map(Conf) -> check(SchemaModule, Conf) when is_map(Conf) ->
%% TODO: remove required %% TODO: remove required
%% fields should state required or not in their schema %% fields should state required or not in their schema
@ -40,7 +40,7 @@ check(SchemaModule, Conf) when is_map(Conf) ->
try try
{ok, hocon_tconf:check_plain(SchemaModule, Conf, Opts)} {ok, hocon_tconf:check_plain(SchemaModule, Conf, Opts)}
catch catch
throw : Reason -> throw:Reason ->
{error, Reason} {error, Reason}
end; end;
check(SchemaModule, HoconText) -> check(SchemaModule, HoconText) ->

View File

@ -22,42 +22,46 @@
-include("types.hrl"). -include("types.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
-export([
-export([ start_link/0 start_link/0,
, stop/0 stop/0
]). ]).
%% Hooks API %% Hooks API
-export([ add/2 -export([
, add/3 add/2,
, add/4 add/3,
, put/2 add/4,
, put/3 put/2,
, put/4 put/3,
, del/2 put/4,
, run/2 del/2,
, run_fold/3 run/2,
, lookup/1 run_fold/3,
]). lookup/1
]).
-export([ callback_action/1 -export([
, callback_filter/1 callback_action/1,
, callback_priority/1 callback_filter/1,
]). callback_priority/1
]).
%% gen_server Function Exports %% gen_server Function Exports
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-export_type([ hookpoint/0 -export_type([
, action/0 hookpoint/0,
, filter/0 action/0,
]). filter/0
]).
%% Multiple callbacks can be registered on a hookpoint. %% Multiple callbacks can be registered on a hookpoint.
%% The execution order depends on the priority value: %% The execution order depends on the priority value:
@ -67,32 +71,36 @@
%% - The execution order is the adding order of callbacks if they have %% - The execution order is the adding order of callbacks if they have
%% equal priority values. %% equal priority values.
-type(hookpoint() :: atom() | binary()). -type hookpoint() :: atom() | binary().
-type(action() :: {module(), atom(), [term()] | undefined}). -type action() :: {module(), atom(), [term()] | undefined}.
-type(filter() :: {module(), atom(), [term()] | undefined}). -type filter() :: {module(), atom(), [term()] | undefined}.
-record(callback, { -record(callback, {
action :: action(), action :: action(),
filter :: maybe(filter()), filter :: maybe(filter()),
priority :: integer() priority :: integer()
}). }).
-type(callback() :: #callback{}). -type callback() :: #callback{}.
-record(hook, { -record(hook, {
name :: hookpoint(), name :: hookpoint(),
callbacks :: list(#callback{}) callbacks :: list(#callback{})
}). }).
-define(TAB, ?MODULE). -define(TAB, ?MODULE).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, gen_server:start_link(
?MODULE, [], [{hibernate_after, 1000}]). {local, ?SERVER},
?MODULE,
[],
[{hibernate_after, 1000}]
).
-spec(stop() -> ok). -spec stop() -> ok.
stop() -> stop() ->
gen_server:stop(?SERVER, normal, infinity). gen_server:stop(?SERVER, normal, infinity).
@ -107,65 +115,63 @@ callback_action(#callback{action = A}) -> A.
callback_filter(#callback{filter = F}) -> F. callback_filter(#callback{filter = F}) -> F.
%% @doc Get callback priority. %% @doc Get callback priority.
callback_priority(#callback{priority= P}) -> P. callback_priority(#callback{priority = P}) -> P.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Hooks API %% Hooks API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Register a callback %% @doc Register a callback
-spec(add(hookpoint(), action() | callback()) -> ok_or_error(already_exists)). -spec add(hookpoint(), action() | callback()) -> ok_or_error(already_exists).
add(HookPoint, Callback) when is_record(Callback, callback) -> add(HookPoint, Callback) when is_record(Callback, callback) ->
gen_server:call(?SERVER, {add, HookPoint, Callback}, infinity); gen_server:call(?SERVER, {add, HookPoint, Callback}, infinity);
add(HookPoint, Action) when is_function(Action); is_tuple(Action) -> add(HookPoint, Action) when is_function(Action); is_tuple(Action) ->
add(HookPoint, #callback{action = Action, priority = 0}). add(HookPoint, #callback{action = Action, priority = 0}).
-spec(add(hookpoint(), action(), filter() | integer() | list()) -spec add(hookpoint(), action(), filter() | integer() | list()) ->
-> ok_or_error(already_exists)). ok_or_error(already_exists).
add(HookPoint, Action, {_M, _F, _A} = Filter) -> add(HookPoint, Action, {_M, _F, _A} = Filter) ->
add(HookPoint, #callback{action = Action, filter = Filter, priority = 0}); add(HookPoint, #callback{action = Action, filter = Filter, priority = 0});
add(HookPoint, Action, Priority) when is_integer(Priority) -> add(HookPoint, Action, Priority) when is_integer(Priority) ->
add(HookPoint, #callback{action = Action, priority = Priority}). add(HookPoint, #callback{action = Action, priority = Priority}).
-spec(add(hookpoint(), action(), filter(), integer()) -spec add(hookpoint(), action(), filter(), integer()) ->
-> ok_or_error(already_exists)). ok_or_error(already_exists).
add(HookPoint, Action, Filter, Priority) when is_integer(Priority) -> add(HookPoint, Action, Filter, Priority) when is_integer(Priority) ->
add(HookPoint, #callback{action = Action, filter = Filter, priority = Priority}). add(HookPoint, #callback{action = Action, filter = Filter, priority = Priority}).
%% @doc Like add/2, it register a callback, discard 'already_exists' error. %% @doc Like add/2, it register a callback, discard 'already_exists' error.
-spec(put(hookpoint(), action() | callback()) -> ok). -spec put(hookpoint(), action() | callback()) -> ok.
put(HookPoint, Callback) when is_record(Callback, callback) -> put(HookPoint, Callback) when is_record(Callback, callback) ->
case add(HookPoint, Callback) of case add(HookPoint, Callback) of
ok -> ok; ok -> ok;
{error, already_exists} -> {error, already_exists} -> gen_server:call(?SERVER, {put, HookPoint, Callback}, infinity)
gen_server:call(?SERVER, {put, HookPoint, Callback}, infinity)
end; end;
put(HookPoint, Action) when is_function(Action); is_tuple(Action) -> put(HookPoint, Action) when is_function(Action); is_tuple(Action) ->
?MODULE:put(HookPoint, #callback{action = Action, priority = 0}). ?MODULE:put(HookPoint, #callback{action = Action, priority = 0}).
-spec(put(hookpoint(), action(), filter() | integer() | list()) -> ok). -spec put(hookpoint(), action(), filter() | integer() | list()) -> ok.
put(HookPoint, Action, {_M, _F, _A} = Filter) -> put(HookPoint, Action, {_M, _F, _A} = Filter) ->
?MODULE:put(HookPoint, #callback{action = Action, filter = Filter, priority = 0}); ?MODULE:put(HookPoint, #callback{action = Action, filter = Filter, priority = 0});
put(HookPoint, Action, Priority) when is_integer(Priority) -> put(HookPoint, Action, Priority) when is_integer(Priority) ->
?MODULE:put(HookPoint, #callback{action = Action, priority = Priority}). ?MODULE:put(HookPoint, #callback{action = Action, priority = Priority}).
-spec(put(hookpoint(), action(), filter(), integer()) -> ok). -spec put(hookpoint(), action(), filter(), integer()) -> ok.
put(HookPoint, Action, Filter, Priority) when is_integer(Priority) -> put(HookPoint, Action, Filter, Priority) when is_integer(Priority) ->
?MODULE:put(HookPoint, #callback{action = Action, filter = Filter, priority = Priority}). ?MODULE:put(HookPoint, #callback{action = Action, filter = Filter, priority = Priority}).
%% @doc Unregister a callback. %% @doc Unregister a callback.
-spec(del(hookpoint(), action() | {module(), atom()}) -> ok). -spec del(hookpoint(), action() | {module(), atom()}) -> ok.
del(HookPoint, Action) -> del(HookPoint, Action) ->
gen_server:cast(?SERVER, {del, HookPoint, Action}). gen_server:cast(?SERVER, {del, HookPoint, Action}).
%% @doc Run hooks. %% @doc Run hooks.
-spec(run(hookpoint(), list(Arg::term())) -> ok). -spec run(hookpoint(), list(Arg :: term())) -> ok.
run(HookPoint, Args) -> run(HookPoint, Args) ->
do_run(lookup(HookPoint), Args). do_run(lookup(HookPoint), Args).
%% @doc Run hooks with Accumulator. %% @doc Run hooks with Accumulator.
-spec(run_fold(hookpoint(), list(Arg::term()), Acc::term()) -> Acc::term()). -spec run_fold(hookpoint(), list(Arg :: term()), Acc :: term()) -> Acc :: term().
run_fold(HookPoint, Args, Acc) -> run_fold(HookPoint, Args, Acc) ->
do_run_fold(lookup(HookPoint), Args, Acc). do_run_fold(lookup(HookPoint), Args, Acc).
@ -187,9 +193,9 @@ do_run_fold([#callback{action = Action, filter = Filter} | Callbacks], Args, Acc
%% stop the hook chain %% stop the hook chain
stop -> Acc; stop -> Acc;
%% stop the hook chain with NewAcc %% stop the hook chain with NewAcc
{stop, NewAcc} -> NewAcc; {stop, NewAcc} -> NewAcc;
%% continue the hook chain with NewAcc %% continue the hook chain with NewAcc
{ok, NewAcc} -> do_run_fold(Callbacks, Args, NewAcc); {ok, NewAcc} -> do_run_fold(Callbacks, Args, NewAcc);
%% continue the hook chain, in following cases: %% continue the hook chain, in following cases:
%% - the filter validation failed with 'false' %% - the filter validation failed with 'false'
%% - the callback returns any term other than 'stop' or {'stop', NewAcc} %% - the callback returns any term other than 'stop' or {'stop', NewAcc}
@ -198,10 +204,9 @@ do_run_fold([#callback{action = Action, filter = Filter} | Callbacks], Args, Acc
do_run_fold([], _Args, Acc) -> do_run_fold([], _Args, Acc) ->
Acc. Acc.
-spec(filter_passed(filter(), Args::term()) -> true | false). -spec filter_passed(filter(), Args :: term()) -> true | false.
filter_passed(undefined, _Args) -> true; filter_passed(undefined, _Args) -> true;
filter_passed(Filter, Args) -> filter_passed(Filter, Args) -> execute(Filter, Args).
execute(Filter, Args).
safe_execute({M, F, A}, Args) -> safe_execute({M, F, A}, Args) ->
try execute({M, F, A}, Args) of try execute({M, F, A}, Args) of
@ -222,12 +227,13 @@ execute({M, F, A}, Args) ->
erlang:apply(M, F, Args ++ A). erlang:apply(M, F, Args ++ A).
%% @doc Lookup callbacks. %% @doc Lookup callbacks.
-spec(lookup(hookpoint()) -> [callback()]). -spec lookup(hookpoint()) -> [callback()].
lookup(HookPoint) -> lookup(HookPoint) ->
case ets:lookup(?TAB, HookPoint) of case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] -> [#hook{callbacks = Callbacks}] ->
Callbacks; Callbacks;
[] -> [] [] ->
[]
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -239,19 +245,23 @@ init([]) ->
{ok, #{}}. {ok, #{}}.
handle_call({add, HookPoint, Callback = #callback{action = {M, F, _}}}, _From, State) -> handle_call({add, HookPoint, Callback = #callback{action = {M, F, _}}}, _From, State) ->
Reply = case lists:any(fun (#callback{action = {M0, F0, _}}) -> Reply =
M0 =:= M andalso F0 =:= F case
end, Callbacks = lookup(HookPoint)) of lists:any(
true -> {error, already_exists}; fun(#callback{action = {M0, F0, _}}) ->
false -> insert_hook(HookPoint, add_callback(Callback, Callbacks)) M0 =:= M andalso F0 =:= F
end, end,
Callbacks = lookup(HookPoint)
)
of
true -> {error, already_exists};
false -> insert_hook(HookPoint, add_callback(Callback, Callbacks))
end,
{reply, Reply, State}; {reply, Reply, State};
handle_call({put, HookPoint, Callback = #callback{action = {M, F, _}}}, _From, State) -> handle_call({put, HookPoint, Callback = #callback{action = {M, F, _}}}, _From, State) ->
Callbacks = del_callback({M, F}, lookup(HookPoint)), Callbacks = del_callback({M, F}, lookup(HookPoint)),
Reply = update_hook(HookPoint, add_callback(Callback, Callbacks)), Reply = update_hook(HookPoint, add_callback(Callback, Callbacks)),
{reply, Reply, State}; {reply, Reply, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", req => Req}), ?SLOG(error, #{msg => "unexpected_call", req => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -264,7 +274,6 @@ handle_cast({del, HookPoint, Action}, State) ->
insert_hook(HookPoint, Callbacks) insert_hook(HookPoint, Callbacks)
end, end,
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", req => Msg}), ?SLOG(error, #{msg => "unexpected_cast", req => Msg}),
{noreply, State}. {noreply, State}.
@ -284,9 +293,10 @@ code_change(_OldVsn, State, _Extra) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
insert_hook(HookPoint, Callbacks) -> insert_hook(HookPoint, Callbacks) ->
ets:insert(?TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok. ets:insert(?TAB, #hook{name = HookPoint, callbacks = Callbacks}),
ok.
update_hook(HookPoint, Callbacks) -> update_hook(HookPoint, Callbacks) ->
Ms = ets:fun2ms(fun ({hook, K, V}) when K =:= HookPoint -> {hook, K, Callbacks} end), Ms = ets:fun2ms(fun({hook, K, V}) when K =:= HookPoint -> {hook, K, Callbacks} end),
ets:select_replace(emqx_hooks, Ms), ets:select_replace(emqx_hooks, Ms),
ok. ok.
@ -295,11 +305,17 @@ add_callback(C, Callbacks) ->
add_callback(C, [], Acc) -> add_callback(C, [], Acc) ->
lists:reverse([C | Acc]); lists:reverse([C | Acc]);
add_callback(C1 = #callback{priority = P1}, [C2 = #callback{priority = P2} | More], Acc) add_callback(C1 = #callback{priority = P1}, [C2 = #callback{priority = P2} | More], Acc) when
when P1 < P2 -> P1 < P2
->
add_callback(C1, More, [C2 | Acc]); add_callback(C1, More, [C2 | Acc]);
add_callback(C1 = #callback{priority = P1, action = MFA1}, [C2 = #callback{priority = P2, action = MFA2} | More], Acc) add_callback(
when P1 =:= P2 andalso MFA1 >= MFA2 -> C1 = #callback{priority = P1, action = MFA1},
[C2 = #callback{priority = P2, action = MFA2} | More],
Acc
) when
P1 =:= P2 andalso MFA1 >= MFA2
->
add_callback(C1, More, [C2 | Acc]); add_callback(C1, More, [C2 | Acc]);
add_callback(C1, More, Acc) -> add_callback(C1, More, Acc) ->
lists:append(lists:reverse(Acc), [C1 | More]). lists:append(lists:reverse(Acc), [C1 | More]).

View File

@ -19,109 +19,109 @@
-compile(inline). -compile(inline).
%% APIs %% APIs
-export([ new/0 -export([
, new/1 new/0,
, contain/2 new/1,
, lookup/2 contain/2,
, insert/3 lookup/2,
, update/3 insert/3,
, resize/2 update/3,
, delete/2 resize/2,
, values/1 delete/2,
, to_list/1 values/1,
, to_list/2 to_list/1,
, size/1 to_list/2,
, max_size/1 size/1,
, is_full/1 max_size/1,
, is_empty/1 is_full/1,
, window/1 is_empty/1,
]). window/1
]).
-export_type([inflight/0]). -export_type([inflight/0]).
-type(key() :: term()). -type key() :: term().
-type(max_size() :: pos_integer()). -type max_size() :: pos_integer().
-opaque(inflight() :: {inflight, max_size(), gb_trees:tree()}). -opaque inflight() :: {inflight, max_size(), gb_trees:tree()}.
-define(INFLIGHT(Tree), {inflight, _MaxSize, Tree}). -define(INFLIGHT(Tree), {inflight, _MaxSize, Tree}).
-define(INFLIGHT(MaxSize, Tree), {inflight, MaxSize, (Tree)}). -define(INFLIGHT(MaxSize, Tree), {inflight, MaxSize, (Tree)}).
-spec(new() -> inflight()). -spec new() -> inflight().
new() -> new(0). new() -> new(0).
-spec(new(non_neg_integer()) -> inflight()). -spec new(non_neg_integer()) -> inflight().
new(MaxSize) when MaxSize >= 0 -> new(MaxSize) when MaxSize >= 0 ->
?INFLIGHT(MaxSize, gb_trees:empty()). ?INFLIGHT(MaxSize, gb_trees:empty()).
-spec(contain(key(), inflight()) -> boolean()). -spec contain(key(), inflight()) -> boolean().
contain(Key, ?INFLIGHT(Tree)) -> contain(Key, ?INFLIGHT(Tree)) ->
gb_trees:is_defined(Key, Tree). gb_trees:is_defined(Key, Tree).
-spec(lookup(key(), inflight()) -> {value, term()} | none). -spec lookup(key(), inflight()) -> {value, term()} | none.
lookup(Key, ?INFLIGHT(Tree)) -> lookup(Key, ?INFLIGHT(Tree)) ->
gb_trees:lookup(Key, Tree). gb_trees:lookup(Key, Tree).
-spec(insert(key(), Val :: term(), inflight()) -> inflight()). -spec insert(key(), Val :: term(), inflight()) -> inflight().
insert(Key, Val, ?INFLIGHT(MaxSize, Tree)) -> insert(Key, Val, ?INFLIGHT(MaxSize, Tree)) ->
?INFLIGHT(MaxSize, gb_trees:insert(Key, Val, Tree)). ?INFLIGHT(MaxSize, gb_trees:insert(Key, Val, Tree)).
-spec(delete(key(), inflight()) -> inflight()). -spec delete(key(), inflight()) -> inflight().
delete(Key, ?INFLIGHT(MaxSize, Tree)) -> delete(Key, ?INFLIGHT(MaxSize, Tree)) ->
?INFLIGHT(MaxSize, gb_trees:delete(Key, Tree)). ?INFLIGHT(MaxSize, gb_trees:delete(Key, Tree)).
-spec(update(key(), Val :: term(), inflight()) -> inflight()). -spec update(key(), Val :: term(), inflight()) -> inflight().
update(Key, Val, ?INFLIGHT(MaxSize, Tree)) -> update(Key, Val, ?INFLIGHT(MaxSize, Tree)) ->
?INFLIGHT(MaxSize, gb_trees:update(Key, Val, Tree)). ?INFLIGHT(MaxSize, gb_trees:update(Key, Val, Tree)).
-spec(resize(integer(), inflight()) -> inflight()). -spec resize(integer(), inflight()) -> inflight().
resize(MaxSize, ?INFLIGHT(Tree)) -> resize(MaxSize, ?INFLIGHT(Tree)) ->
?INFLIGHT(MaxSize, Tree). ?INFLIGHT(MaxSize, Tree).
-spec(is_full(inflight()) -> boolean()). -spec is_full(inflight()) -> boolean().
is_full(?INFLIGHT(0, _Tree)) -> is_full(?INFLIGHT(0, _Tree)) ->
false; false;
is_full(?INFLIGHT(MaxSize, Tree)) -> is_full(?INFLIGHT(MaxSize, Tree)) ->
MaxSize =< gb_trees:size(Tree). MaxSize =< gb_trees:size(Tree).
-spec(is_empty(inflight()) -> boolean()). -spec is_empty(inflight()) -> boolean().
is_empty(?INFLIGHT(Tree)) -> is_empty(?INFLIGHT(Tree)) ->
gb_trees:is_empty(Tree). gb_trees:is_empty(Tree).
-spec(smallest(inflight()) -> {key(), term()}). -spec smallest(inflight()) -> {key(), term()}.
smallest(?INFLIGHT(Tree)) -> smallest(?INFLIGHT(Tree)) ->
gb_trees:smallest(Tree). gb_trees:smallest(Tree).
-spec(largest(inflight()) -> {key(), term()}). -spec largest(inflight()) -> {key(), term()}.
largest(?INFLIGHT(Tree)) -> largest(?INFLIGHT(Tree)) ->
gb_trees:largest(Tree). gb_trees:largest(Tree).
-spec(values(inflight()) -> list()). -spec values(inflight()) -> list().
values(?INFLIGHT(Tree)) -> values(?INFLIGHT(Tree)) ->
gb_trees:values(Tree). gb_trees:values(Tree).
-spec(to_list(inflight()) -> list({key(), term()})). -spec to_list(inflight()) -> list({key(), term()}).
to_list(?INFLIGHT(Tree)) -> to_list(?INFLIGHT(Tree)) ->
gb_trees:to_list(Tree). gb_trees:to_list(Tree).
-spec(to_list(fun(), inflight()) -> list({key(), term()})). -spec to_list(fun(), inflight()) -> list({key(), term()}).
to_list(SortFun, ?INFLIGHT(Tree)) -> to_list(SortFun, ?INFLIGHT(Tree)) ->
lists:sort(SortFun, gb_trees:to_list(Tree)). lists:sort(SortFun, gb_trees:to_list(Tree)).
-spec(window(inflight()) -> list()). -spec window(inflight()) -> list().
window(Inflight = ?INFLIGHT(Tree)) -> window(Inflight = ?INFLIGHT(Tree)) ->
case gb_trees:is_empty(Tree) of case gb_trees:is_empty(Tree) of
true -> []; true -> [];
false -> [Key || {Key, _Val} <- [smallest(Inflight), largest(Inflight)]] false -> [Key || {Key, _Val} <- [smallest(Inflight), largest(Inflight)]]
end. end.
-spec(size(inflight()) -> non_neg_integer()). -spec size(inflight()) -> non_neg_integer().
size(?INFLIGHT(Tree)) -> size(?INFLIGHT(Tree)) ->
gb_trees:size(Tree). gb_trees:size(Tree).
-spec(max_size(inflight()) -> non_neg_integer()). -spec max_size(inflight()) -> non_neg_integer().
max_size(?INFLIGHT(MaxSize, _Tree)) -> max_size(?INFLIGHT(MaxSize, _Tree)) ->
MaxSize. MaxSize.

View File

@ -18,52 +18,58 @@
-compile(inline). -compile(inline).
-export([ encode/1 -export([
, encode/2 encode/1,
, safe_encode/1 encode/2,
, safe_encode/2 safe_encode/1,
]). safe_encode/2
]).
-compile({inline, -compile(
[ encode/1 {inline, [
, encode/2 encode/1,
]}). encode/2
]}
).
-export([ decode/1 -export([
, decode/2 decode/1,
, safe_decode/1 decode/2,
, safe_decode/2 safe_decode/1,
]). safe_decode/2
]).
-compile({inline, -compile(
[ decode/1 {inline, [
, decode/2 decode/1,
]}). decode/2
]}
).
-type(encode_options() :: jiffy:encode_options()). -type encode_options() :: jiffy:encode_options().
-type(decode_options() :: jiffy:decode_options()). -type decode_options() :: jiffy:decode_options().
-type(json_text() :: iolist() | binary()). -type json_text() :: iolist() | binary().
-type(json_term() :: jiffy:jiffy_decode_result()). -type json_term() :: jiffy:jiffy_decode_result().
-export_type([json_text/0, json_term/0]). -export_type([json_text/0, json_term/0]).
-export_type([decode_options/0, encode_options/0]). -export_type([decode_options/0, encode_options/0]).
-spec(encode(json_term()) -> json_text()). -spec encode(json_term()) -> json_text().
encode(Term) -> encode(Term) ->
encode(Term, [force_utf8]). encode(Term, [force_utf8]).
-spec(encode(json_term(), encode_options()) -> json_text()). -spec encode(json_term(), encode_options()) -> json_text().
encode(Term, Opts) -> encode(Term, Opts) ->
to_binary(jiffy:encode(to_ejson(Term), Opts)). to_binary(jiffy:encode(to_ejson(Term), Opts)).
-spec(safe_encode(json_term()) -spec safe_encode(json_term()) ->
-> {ok, json_text()} | {error, Reason :: term()}). {ok, json_text()} | {error, Reason :: term()}.
safe_encode(Term) -> safe_encode(Term) ->
safe_encode(Term, [force_utf8]). safe_encode(Term, [force_utf8]).
-spec(safe_encode(json_term(), encode_options()) -spec safe_encode(json_term(), encode_options()) ->
-> {ok, json_text()} | {error, Reason :: term()}). {ok, json_text()} | {error, Reason :: term()}.
safe_encode(Term, Opts) -> safe_encode(Term, Opts) ->
try encode(Term, Opts) of try encode(Term, Opts) of
Json -> {ok, Json} Json -> {ok, Json}
@ -72,20 +78,20 @@ safe_encode(Term, Opts) ->
{error, Reason} {error, Reason}
end. end.
-spec(decode(json_text()) -> json_term()). -spec decode(json_text()) -> json_term().
decode(Json) -> decode(Json, []). decode(Json) -> decode(Json, []).
-spec(decode(json_text(), decode_options()) -> json_term()). -spec decode(json_text(), decode_options()) -> json_term().
decode(Json, Opts) -> decode(Json, Opts) ->
from_ejson(jiffy:decode(Json, Opts)). from_ejson(jiffy:decode(Json, Opts)).
-spec(safe_decode(json_text()) -spec safe_decode(json_text()) ->
-> {ok, json_term()} | {error, Reason :: term()}). {ok, json_term()} | {error, Reason :: term()}.
safe_decode(Json) -> safe_decode(Json) ->
safe_decode(Json, []). safe_decode(Json, []).
-spec(safe_decode(json_text(), decode_options()) -spec safe_decode(json_text(), decode_options()) ->
-> {ok, json_term()} | {error, Reason :: term()}). {ok, json_term()} | {error, Reason :: term()}.
safe_decode(Json, Opts) -> safe_decode(Json, Opts) ->
try decode(Json, Opts) of try decode(Json, Opts) of
Term -> {ok, Term} Term -> {ok, Term}
@ -98,18 +104,21 @@ safe_decode(Json, Opts) ->
%% Helpers %% Helpers
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-compile({inline, -compile(
[ to_ejson/1 {inline, [
, from_ejson/1 to_ejson/1,
]}). from_ejson/1
]}
).
to_ejson([{}]) -> to_ejson([{}]) ->
{[]}; {[]};
to_ejson([{_, _}|_] = L) -> to_ejson([{_, _} | _] = L) ->
{[{K, to_ejson(V)} || {K, V} <- L ]}; {[{K, to_ejson(V)} || {K, V} <- L]};
to_ejson(L) when is_list(L) -> to_ejson(L) when is_list(L) ->
[to_ejson(E) || E <- L]; [to_ejson(E) || E <- L];
to_ejson(T) -> T. to_ejson(T) ->
T.
from_ejson(L) when is_list(L) -> from_ejson(L) when is_list(L) ->
[from_ejson(E) || E <- L]; [from_ejson(E) || E <- L];
@ -117,7 +126,8 @@ from_ejson({[]}) ->
[{}]; [{}];
from_ejson({L}) -> from_ejson({L}) ->
[{Name, from_ejson(Value)} || {Name, Value} <- L]; [{Name, from_ejson(Value)} || {Name, Value} <- L];
from_ejson(T) -> T. from_ejson(T) ->
T.
to_binary(B) when is_binary(B) -> B; to_binary(B) when is_binary(B) -> B;
to_binary(L) when is_list(L) -> to_binary(L) when is_list(L) ->

View File

@ -16,49 +16,55 @@
-module(emqx_keepalive). -module(emqx_keepalive).
-export([ init/1 -export([
, init/2 init/1,
, info/1 init/2,
, info/2 info/1,
, check/2 info/2,
, set/3 check/2,
]). set/3
]).
-elvis([{elvis_style, no_if_expression, disable}]). -elvis([{elvis_style, no_if_expression, disable}]).
-export_type([keepalive/0]). -export_type([keepalive/0]).
-record(keepalive, { -record(keepalive, {
interval :: pos_integer(), interval :: pos_integer(),
statval :: non_neg_integer(), statval :: non_neg_integer(),
repeat :: non_neg_integer() repeat :: non_neg_integer()
}). }).
-opaque(keepalive() :: #keepalive{}). -opaque keepalive() :: #keepalive{}.
%% @doc Init keepalive. %% @doc Init keepalive.
-spec(init(Interval :: non_neg_integer()) -> keepalive()). -spec init(Interval :: non_neg_integer()) -> keepalive().
init(Interval) -> init(0, Interval). init(Interval) -> init(0, Interval).
%% @doc Init keepalive. %% @doc Init keepalive.
-spec(init(StatVal :: non_neg_integer(), Interval :: non_neg_integer()) -> keepalive()). -spec init(StatVal :: non_neg_integer(), Interval :: non_neg_integer()) -> keepalive().
init(StatVal, Interval) when Interval > 0 -> init(StatVal, Interval) when Interval > 0 ->
#keepalive{interval = Interval, #keepalive{
statval = StatVal, interval = Interval,
repeat = 0}. statval = StatVal,
repeat = 0
}.
%% @doc Get Info of the keepalive. %% @doc Get Info of the keepalive.
-spec(info(keepalive()) -> emqx_types:infos()). -spec info(keepalive()) -> emqx_types:infos().
info(#keepalive{interval = Interval, info(#keepalive{
statval = StatVal, interval = Interval,
repeat = Repeat}) -> statval = StatVal,
#{interval => Interval, repeat = Repeat
statval => StatVal, }) ->
repeat => Repeat #{
}. interval => Interval,
statval => StatVal,
repeat => Repeat
}.
-spec(info(interval | statval | repeat, keepalive()) -spec info(interval | statval | repeat, keepalive()) ->
-> non_neg_integer()). non_neg_integer().
info(interval, #keepalive{interval = Interval}) -> info(interval, #keepalive{interval = Interval}) ->
Interval; Interval;
info(statval, #keepalive{statval = StatVal}) -> info(statval, #keepalive{statval = StatVal}) ->
@ -67,16 +73,22 @@ info(repeat, #keepalive{repeat = Repeat}) ->
Repeat. Repeat.
%% @doc Check keepalive. %% @doc Check keepalive.
-spec(check(non_neg_integer(), keepalive()) -spec check(non_neg_integer(), keepalive()) ->
-> {ok, keepalive()} | {error, timeout}). {ok, keepalive()} | {error, timeout}.
check(NewVal, KeepAlive = #keepalive{statval = OldVal, check(
repeat = Repeat}) -> NewVal,
KeepAlive = #keepalive{
statval = OldVal,
repeat = Repeat
}
) ->
if if
NewVal =/= OldVal -> NewVal =/= OldVal ->
{ok, KeepAlive#keepalive{statval = NewVal, repeat = 0}}; {ok, KeepAlive#keepalive{statval = NewVal, repeat = 0}};
Repeat < 1 -> Repeat < 1 ->
{ok, KeepAlive#keepalive{repeat = Repeat + 1}}; {ok, KeepAlive#keepalive{repeat = Repeat + 1}};
true -> {error, timeout} true ->
{error, timeout}
end. end.
%% from mqtt-v3.1.1 specific %% from mqtt-v3.1.1 specific
@ -91,6 +103,6 @@ check(NewVal, KeepAlive = #keepalive{statval = OldVal,
%% typically this is a few minutes. %% typically this is a few minutes.
%% The maximum value is (65535s) 18 hours 12 minutes and 15 seconds. %% The maximum value is (65535s) 18 hours 12 minutes and 15 seconds.
%% @doc Update keepalive's interval %% @doc Update keepalive's interval
-spec(set(interval, non_neg_integer(), keepalive()) -> keepalive()). -spec set(interval, non_neg_integer(), keepalive()) -> keepalive().
set(interval, Interval, KeepAlive) when Interval >= 0 andalso Interval =< 65535000 -> set(interval, Interval, KeepAlive) when Interval >= 0 andalso Interval =< 65535000 ->
KeepAlive#keepalive{interval = Interval}. KeepAlive#keepalive{interval = Interval}.

View File

@ -26,33 +26,37 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
{ok, {{one_for_one, 10, 100}, {ok, {
%% always start emqx_config_handler first to load the emqx.conf to emqx_config {one_for_one, 10, 100},
[ child_spec(emqx_config_handler, worker) %% always start emqx_config_handler first to load the emqx.conf to emqx_config
, child_spec(emqx_pool_sup, supervisor) [
, child_spec(emqx_hooks, worker) child_spec(emqx_config_handler, worker),
, child_spec(emqx_stats, worker) child_spec(emqx_pool_sup, supervisor),
, child_spec(emqx_metrics, worker) child_spec(emqx_hooks, worker),
, child_spec(emqx_ctl, worker) child_spec(emqx_stats, worker),
]}}. child_spec(emqx_metrics, worker),
child_spec(emqx_ctl, worker)
]
}}.
child_spec(M, Type) -> child_spec(M, Type) ->
child_spec(M, Type, []). child_spec(M, Type, []).
child_spec(M, worker, Args) -> child_spec(M, worker, Args) ->
#{id => M, #{
start => {M, start_link, Args}, id => M,
restart => permanent, start => {M, start_link, Args},
shutdown => 5000, restart => permanent,
type => worker, shutdown => 5000,
modules => [M] type => worker,
}; modules => [M]
};
child_spec(M, supervisor, Args) -> child_spec(M, supervisor, Args) ->
#{id => M, #{
start => {M, start_link, Args}, id => M,
restart => permanent, start => {M, start_link, Args},
shutdown => infinity, restart => permanent,
type => supervisor, shutdown => infinity,
modules => [M] type => supervisor,
}. modules => [M]
}.

View File

@ -19,39 +19,46 @@
-include("types.hrl"). -include("types.hrl").
-export([ init/2 -export([
, init/4 %% XXX: Compatible with before 4.2 version init/2,
, info/1 %% XXX: Compatible with before 4.2 version
, check/2 init/4,
]). info/1,
check/2
]).
-record(limiter, { -record(limiter, {
%% Zone %% Zone
zone :: atom(), zone :: atom(),
%% Checkers %% Checkers
checkers :: [checker()] checkers :: [checker()]
}). }).
-type(checker() :: #{ name := name() -type checker() :: #{
, capacity := non_neg_integer() name := name(),
, interval := non_neg_integer() capacity := non_neg_integer(),
, consumer := esockd_rate_limit:bucket() | atom() interval := non_neg_integer(),
}). consumer := esockd_rate_limit:bucket() | atom()
}.
-type(name() :: conn_bytes_in -type name() ::
| conn_messages_in conn_bytes_in
| conn_messages_routing | conn_messages_in
| overall_messages_routing | conn_messages_routing
). | overall_messages_routing.
-type(policy() :: [{name(), esockd_rate_limit:config()}]). -type policy() :: [{name(), esockd_rate_limit:config()}].
-type(info() :: #{name() := -type info() :: #{
#{tokens := non_neg_integer(), name() :=
capacity := non_neg_integer(), #{
interval := non_neg_integer()}}). tokens := non_neg_integer(),
capacity := non_neg_integer(),
interval := non_neg_integer()
}
}.
-type(limiter() :: #limiter{}). -type limiter() :: #limiter{}.
-dialyzer({nowarn_function, [consume/3]}). -dialyzer({nowarn_function, [consume/3]}).
@ -59,17 +66,25 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(init(atom(), -spec init(
maybe(esockd_rate_limit:config()), atom(),
maybe(esockd_rate_limit:config()), policy()) maybe(esockd_rate_limit:config()),
-> maybe(limiter())). maybe(esockd_rate_limit:config()),
policy()
) ->
maybe(limiter()).
init(Zone, PubLimit, BytesIn, Specs) -> init(Zone, PubLimit, BytesIn, Specs) ->
Merged = maps:merge(#{conn_messages_in => PubLimit, Merged = maps:merge(
conn_bytes_in => BytesIn}, maps:from_list(Specs)), #{
conn_messages_in => PubLimit,
conn_bytes_in => BytesIn
},
maps:from_list(Specs)
),
Filtered = maps:filter(fun(_, V) -> V /= undefined end, Merged), Filtered = maps:filter(fun(_, V) -> V /= undefined end, Merged),
init(Zone, maps:to_list(Filtered)). init(Zone, maps:to_list(Filtered)).
-spec(init(atom(), policy()) -> maybe(limiter())). -spec init(atom(), policy()) -> maybe(limiter()).
init(_Zone, []) -> init(_Zone, []) ->
undefined; undefined;
init(Zone, Specs) -> init(Zone, Specs) ->
@ -91,15 +106,19 @@ do_init_checker(Zone, {Name, {Capacity, Interval}}) ->
Ck#{consumer => esockd_rate_limit:new(Capacity / Interval, Capacity)} Ck#{consumer => esockd_rate_limit:new(Capacity / Interval, Capacity)}
end. end.
-spec(info(limiter()) -> info()). -spec info(limiter()) -> info().
info(#limiter{zone = Zone, checkers = Cks}) -> info(#limiter{zone = Zone, checkers = Cks}) ->
maps:from_list([get_info(Zone, Ck) || Ck <- Cks]). maps:from_list([get_info(Zone, Ck) || Ck <- Cks]).
-spec(check(#{cnt := Cnt :: non_neg_integer(), -spec check(
oct := Oct :: non_neg_integer()}, #{
Limiter :: limiter()) cnt := Cnt :: non_neg_integer(),
-> {ok, NLimiter :: limiter()} oct := Oct :: non_neg_integer()
| {pause, MilliSecs :: non_neg_integer(), NLimiter :: limiter()}). },
Limiter :: limiter()
) ->
{ok, NLimiter :: limiter()}
| {pause, MilliSecs :: non_neg_integer(), NLimiter :: limiter()}.
check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{checkers = Cks}) -> check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{checkers = Cks}) ->
{Pauses, NCks} = do_check(Cnt, Oct, Cks, [], []), {Pauses, NCks} = do_check(Cnt, Oct, Cks, [], []),
case lists:max(Pauses) of case lists:max(Pauses) of
@ -112,16 +131,20 @@ check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{checkers = Cks}) ->
%% @private %% @private
do_check(_, _, [], Pauses, NCks) -> do_check(_, _, [], Pauses, NCks) ->
{Pauses, lists:reverse(NCks)}; {Pauses, lists:reverse(NCks)};
do_check(Pubs, Bytes, [Ck|More], Pauses, Acc) -> do_check(Pubs, Bytes, [Ck | More], Pauses, Acc) ->
{I, NConsumer} = consume(Pubs, Bytes, Ck), {I, NConsumer} = consume(Pubs, Bytes, Ck),
do_check(Pubs, Bytes, More, [I|Pauses], [Ck#{consumer := NConsumer}|Acc]). do_check(Pubs, Bytes, More, [I | Pauses], [Ck#{consumer := NConsumer} | Acc]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
consume(Pubs, Bytes, #{name := Name, consumer := Cons}) -> consume(Pubs, Bytes, #{name := Name, consumer := Cons}) ->
Tokens = case is_message_limiter(Name) of true -> Pubs; _ -> Bytes end, Tokens =
case is_message_limiter(Name) of
true -> Pubs;
_ -> Bytes
end,
case Tokens =:= 0 of case Tokens =:= 0 of
true -> true ->
{0, Cons}; {0, Cons};
@ -135,15 +158,22 @@ consume(Pubs, Bytes, #{name := Name, consumer := Cons}) ->
end end
end. end.
get_info(Zone, #{name := Name, capacity := Cap, get_info(Zone, #{
interval := Intv, consumer := Cons}) -> name := Name,
Info = case is_overall_limiter(Name) of capacity := Cap,
true -> esockd_limiter:lookup({Zone, Name}); interval := Intv,
_ -> esockd_rate_limit:info(Cons) consumer := Cons
end, }) ->
{Name, #{capacity => Cap, Info =
interval => Intv, case is_overall_limiter(Name) of
tokens => maps:get(tokens, Info)}}. true -> esockd_limiter:lookup({Zone, Name});
_ -> esockd_rate_limit:info(Cons)
end,
{Name, #{
capacity => Cap,
interval => Intv,
tokens => maps:get(tokens, Info)
}}.
is_overall_limiter(overall_messages_routing) -> true; is_overall_limiter(overall_messages_routing) -> true;
is_overall_limiter(_) -> false. is_overall_limiter(_) -> false.

View File

@ -23,37 +23,40 @@
-include("logger.hrl"). -include("logger.hrl").
%% APIs %% APIs
-export([ list/0 -export([
, start/0 list/0,
, restart/0 start/0,
, stop/0 restart/0,
, is_running/1 stop/0,
, current_conns/2 is_running/1,
, max_conns/2 current_conns/2,
, id_example/0 max_conns/2,
]). id_example/0
]).
-export([ start_listener/1 -export([
, start_listener/3 start_listener/1,
, stop_listener/1 start_listener/3,
, stop_listener/3 stop_listener/1,
, restart_listener/1 stop_listener/3,
, restart_listener/3 restart_listener/1,
, has_enabled_listener_conf_by_type/1 restart_listener/3,
]). has_enabled_listener_conf_by_type/1
]).
-export([ listener_id/2 -export([
, parse_listener_id/1 listener_id/2,
]). parse_listener_id/1
]).
-export([post_config_update/5]). -export([post_config_update/5]).
-export([format_addr/1]). -export([format_addr/1]).
-define(CONF_KEY_PATH, [listeners]). -define(CONF_KEY_PATH, [listeners]).
-define(TYPES_STRING, ["tcp","ssl","ws","wss","quic"]). -define(TYPES_STRING, ["tcp", "ssl", "ws", "wss", "quic"]).
-spec(id_example() -> atom()). -spec id_example() -> atom().
id_example() -> id_example() ->
id_example(list()). id_example(list()).
@ -66,7 +69,7 @@ id_example([_ | Listeners]) ->
id_example(Listeners). id_example(Listeners).
%% @doc List configured listeners. %% @doc List configured listeners.
-spec(list() -> [{ListenerId :: atom(), ListenerConf :: map()}]). -spec list() -> [{ListenerId :: atom(), ListenerConf :: map()}].
list() -> list() ->
[{listener_id(Type, LName), LConf} || {Type, LName, LConf} <- do_list()]. [{listener_id(Type, LName), LConf} || {Type, LName, LConf} <- do_list()].
@ -75,37 +78,45 @@ do_list() ->
lists:append([list(Type, maps:to_list(Conf)) || {Type, Conf} <- Listeners]). lists:append([list(Type, maps:to_list(Conf)) || {Type, Conf} <- Listeners]).
list(Type, Conf) -> list(Type, Conf) ->
[begin [
Running = is_running(Type, listener_id(Type, LName), LConf), begin
{Type, LName, maps:put(running, Running, LConf)} Running = is_running(Type, listener_id(Type, LName), LConf),
end || {LName, LConf} <- Conf, is_map(LConf)]. {Type, LName, maps:put(running, Running, LConf)}
end
|| {LName, LConf} <- Conf, is_map(LConf)
].
-spec is_running(ListenerId :: atom()) -> boolean() | {error, no_found}. -spec is_running(ListenerId :: atom()) -> boolean() | {error, no_found}.
is_running(ListenerId) -> is_running(ListenerId) ->
case lists:filtermap(fun({_Type, Id, #{running := IsRunning}}) -> case
Id =:= ListenerId andalso {true, IsRunning} lists:filtermap(
end, do_list()) of fun({_Type, Id, #{running := IsRunning}}) ->
Id =:= ListenerId andalso {true, IsRunning}
end,
do_list()
)
of
[IsRunning] -> IsRunning; [IsRunning] -> IsRunning;
[] -> {error, not_found} [] -> {error, not_found}
end. end.
is_running(Type, ListenerId, #{bind := ListenOn}) when Type =:= tcp; Type =:= ssl -> is_running(Type, ListenerId, #{bind := ListenOn}) when Type =:= tcp; Type =:= ssl ->
try esockd:listener({ListenerId, ListenOn}) of try esockd:listener({ListenerId, ListenOn}) of
Pid when is_pid(Pid)-> Pid when is_pid(Pid) ->
true true
catch _:_ -> catch
false _:_ ->
false
end; end;
is_running(Type, ListenerId, _Conf) when Type =:= ws; Type =:= wss -> is_running(Type, ListenerId, _Conf) when Type =:= ws; Type =:= wss ->
try try
Info = ranch:info(ListenerId), Info = ranch:info(ListenerId),
proplists:get_value(status, Info) =:= running proplists:get_value(status, Info) =:= running
catch _:_ -> catch
false _:_ ->
false
end; end;
is_running(quic, _ListenerId, _Conf) ->
is_running(quic, _ListenerId, _Conf)->
%% TODO: quic support %% TODO: quic support
{error, no_found}. {error, no_found}.
@ -132,7 +143,7 @@ max_conns(_, _, _) ->
{error, not_support}. {error, not_support}.
%% @doc Start all listeners. %% @doc Start all listeners.
-spec(start() -> ok). -spec start() -> ok.
start() -> start() ->
%% The ?MODULE:start/0 will be called by emqx_app when emqx get started, %% The ?MODULE:start/0 will be called by emqx_app when emqx get started,
%% so we install the config handler here. %% so we install the config handler here.
@ -146,31 +157,39 @@ start_listener(ListenerId) ->
-spec start_listener(atom(), atom(), map()) -> ok | {error, term()}. -spec start_listener(atom(), atom(), map()) -> ok | {error, term()}.
start_listener(Type, ListenerName, #{bind := Bind} = Conf) -> start_listener(Type, ListenerName, #{bind := Bind} = Conf) ->
case do_start_listener(Type, ListenerName, Conf) of case do_start_listener(Type, ListenerName, Conf) of
{ok, {skipped, Reason}} when Reason =:= listener_disabled; {ok, {skipped, Reason}} when
Reason =:= quic_app_missing -> Reason =:= listener_disabled;
console_print("Listener ~ts is NOT started due to: ~p~n.", Reason =:= quic_app_missing
[listener_id(Type, ListenerName), Reason]); ->
console_print(
"Listener ~ts is NOT started due to: ~p~n.",
[listener_id(Type, ListenerName), Reason]
);
{ok, _} -> {ok, _} ->
console_print("Listener ~ts on ~ts started.~n", console_print(
[listener_id(Type, ListenerName), format_addr(Bind)]); "Listener ~ts on ~ts started.~n",
[listener_id(Type, ListenerName), format_addr(Bind)]
);
{error, {already_started, Pid}} -> {error, {already_started, Pid}} ->
{error, {already_started, Pid}}; {error, {already_started, Pid}};
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start listener ~ts on ~ts: ~0p~n", ?ELOG(
[listener_id(Type, ListenerName), format_addr(Bind), Reason]), "Failed to start listener ~ts on ~ts: ~0p~n",
[listener_id(Type, ListenerName), format_addr(Bind), Reason]
),
error(Reason) error(Reason)
end. end.
%% @doc Restart all listeners %% @doc Restart all listeners
-spec(restart() -> ok). -spec restart() -> ok.
restart() -> restart() ->
foreach_listeners(fun restart_listener/3). foreach_listeners(fun restart_listener/3).
-spec(restart_listener(atom()) -> ok | {error, term()}). -spec restart_listener(atom()) -> ok | {error, term()}.
restart_listener(ListenerId) -> restart_listener(ListenerId) ->
apply_on_listener(ListenerId, fun restart_listener/3). apply_on_listener(ListenerId, fun restart_listener/3).
-spec(restart_listener(atom(), atom(), map()) -> ok | {error, term()}). -spec restart_listener(atom(), atom(), map()) -> ok | {error, term()}.
restart_listener(Type, ListenerName, {OldConf, NewConf}) -> restart_listener(Type, ListenerName, {OldConf, NewConf}) ->
restart_listener(Type, ListenerName, OldConf, NewConf); restart_listener(Type, ListenerName, OldConf, NewConf);
restart_listener(Type, ListenerName, Conf) -> restart_listener(Type, ListenerName, Conf) ->
@ -184,30 +203,34 @@ restart_listener(Type, ListenerName, OldConf, NewConf) ->
end. end.
%% @doc Stop all listeners. %% @doc Stop all listeners.
-spec(stop() -> ok). -spec stop() -> ok.
stop() -> stop() ->
%% The ?MODULE:stop/0 will be called by emqx_app when emqx is going to shutdown, %% The ?MODULE:stop/0 will be called by emqx_app when emqx is going to shutdown,
%% so we uninstall the config handler here. %% so we uninstall the config handler here.
_ = emqx_config_handler:remove_handler(?CONF_KEY_PATH), _ = emqx_config_handler:remove_handler(?CONF_KEY_PATH),
foreach_listeners(fun stop_listener/3). foreach_listeners(fun stop_listener/3).
-spec(stop_listener(atom()) -> ok | {error, term()}). -spec stop_listener(atom()) -> ok | {error, term()}.
stop_listener(ListenerId) -> stop_listener(ListenerId) ->
apply_on_listener(ListenerId, fun stop_listener/3). apply_on_listener(ListenerId, fun stop_listener/3).
stop_listener(Type, ListenerName, #{bind := Bind} = Conf) -> stop_listener(Type, ListenerName, #{bind := Bind} = Conf) ->
case do_stop_listener(Type, ListenerName, Conf) of case do_stop_listener(Type, ListenerName, Conf) of
ok -> ok ->
console_print("Listener ~ts on ~ts stopped.~n", console_print(
[listener_id(Type, ListenerName), format_addr(Bind)]), "Listener ~ts on ~ts stopped.~n",
[listener_id(Type, ListenerName), format_addr(Bind)]
),
ok; ok;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop listener ~ts on ~ts: ~0p~n", ?ELOG(
[listener_id(Type, ListenerName), format_addr(Bind), Reason]), "Failed to stop listener ~ts on ~ts: ~0p~n",
[listener_id(Type, ListenerName), format_addr(Bind), Reason]
),
{error, Reason} {error, Reason}
end. end.
-spec(do_stop_listener(atom(), atom(), map()) -> ok | {error, term()}). -spec do_stop_listener(atom(), atom(), map()) -> ok | {error, term()}.
do_stop_listener(Type, ListenerName, #{bind := ListenOn}) when Type == tcp; Type == ssl -> do_stop_listener(Type, ListenerName, #{bind := ListenOn}) when Type == tcp; Type == ssl ->
esockd:close(listener_id(Type, ListenerName), ListenOn); esockd:close(listener_id(Type, ListenerName), ListenOn);
do_stop_listener(Type, ListenerName, _Conf) when Type == ws; Type == wss -> do_stop_listener(Type, ListenerName, _Conf) when Type == ws; Type == wss ->
@ -222,21 +245,29 @@ console_print(_Fmt, _Args) -> ok.
-endif. -endif.
%% Start MQTT/TCP listener %% Start MQTT/TCP listener
-spec(do_start_listener(atom(), atom(), map()) -spec do_start_listener(atom(), atom(), map()) ->
-> {ok, pid() | {skipped, atom()}} | {error, term()}). {ok, pid() | {skipped, atom()}} | {error, term()}.
do_start_listener(_Type, _ListenerName, #{enabled := false}) -> do_start_listener(_Type, _ListenerName, #{enabled := false}) ->
{ok, {skipped, listener_disabled}}; {ok, {skipped, listener_disabled}};
do_start_listener(Type, ListenerName, #{bind := ListenOn} = Opts) do_start_listener(Type, ListenerName, #{bind := ListenOn} = Opts) when
when Type == tcp; Type == ssl -> Type == tcp; Type == ssl
esockd:open(listener_id(Type, ListenerName), ListenOn, merge_default(esockd_opts(Type, Opts)), ->
{emqx_connection, start_link, esockd:open(
[#{listener => {Type, ListenerName}, listener_id(Type, ListenerName),
zone => zone(Opts), ListenOn,
limiter => limiter(Opts)}]}); merge_default(esockd_opts(Type, Opts)),
{emqx_connection, start_link, [
#{
listener => {Type, ListenerName},
zone => zone(Opts),
limiter => limiter(Opts)
}
]}
);
%% Start MQTT/WS listener %% Start MQTT/WS listener
do_start_listener(Type, ListenerName, #{bind := ListenOn} = Opts) do_start_listener(Type, ListenerName, #{bind := ListenOn} = Opts) when
when Type == ws; Type == wss -> Type == ws; Type == wss
->
Id = listener_id(Type, ListenerName), Id = listener_id(Type, ListenerName),
RanchOpts = ranch_opts(Type, ListenOn, Opts), RanchOpts = ranch_opts(Type, ListenOn, Opts),
WsOpts = ws_opts(Type, ListenerName, Opts), WsOpts = ws_opts(Type, ListenerName, Opts),
@ -244,31 +275,36 @@ do_start_listener(Type, ListenerName, #{bind := ListenOn} = Opts)
ws -> cowboy:start_clear(Id, RanchOpts, WsOpts); ws -> cowboy:start_clear(Id, RanchOpts, WsOpts);
wss -> cowboy:start_tls(Id, RanchOpts, WsOpts) wss -> cowboy:start_tls(Id, RanchOpts, WsOpts)
end; end;
%% Start MQTT/QUIC listener %% Start MQTT/QUIC listener
do_start_listener(quic, ListenerName, #{bind := ListenOn} = Opts) -> do_start_listener(quic, ListenerName, #{bind := ListenOn} = Opts) ->
case [ A || {quicer, _, _} = A<-application:which_applications() ] of case [A || {quicer, _, _} = A <- application:which_applications()] of
[_] -> [_] ->
DefAcceptors = erlang:system_info(schedulers_online) * 8, DefAcceptors = erlang:system_info(schedulers_online) * 8,
ListenOpts = [ {cert, maps:get(certfile, Opts)} ListenOpts = [
, {key, maps:get(keyfile, Opts)} {cert, maps:get(certfile, Opts)},
, {alpn, ["mqtt"]} {key, maps:get(keyfile, Opts)},
, {conn_acceptors, lists:max([DefAcceptors, maps:get(acceptors, Opts, 0)])} {alpn, ["mqtt"]},
, {idle_timeout_ms, {conn_acceptors, lists:max([DefAcceptors, maps:get(acceptors, Opts, 0)])},
lists:max([ {idle_timeout_ms,
emqx_config:get_zone_conf(zone(Opts), [mqtt, idle_timeout]) * 3, lists:max([
timer:seconds(maps:get(idle_timeout, Opts))])} emqx_config:get_zone_conf(zone(Opts), [mqtt, idle_timeout]) * 3,
], timer:seconds(maps:get(idle_timeout, Opts))
ConnectionOpts = #{ conn_callback => emqx_quic_connection ])}
, peer_unidi_stream_count => 1 ],
, peer_bidi_stream_count => 10 ConnectionOpts = #{
, zone => zone(Opts) conn_callback => emqx_quic_connection,
, listener => {quic, ListenerName} peer_unidi_stream_count => 1,
, limiter => limiter(Opts) peer_bidi_stream_count => 10,
}, zone => zone(Opts),
listener => {quic, ListenerName},
limiter => limiter(Opts)
},
StreamOpts = [{stream_callback, emqx_quic_stream}], StreamOpts = [{stream_callback, emqx_quic_stream}],
quicer:start_listener(listener_id(quic, ListenerName), quicer:start_listener(
port(ListenOn), {ListenOpts, ConnectionOpts, StreamOpts}); listener_id(quic, ListenerName),
port(ListenOn),
{ListenOpts, ConnectionOpts, StreamOpts}
);
[] -> [] ->
{ok, {skipped, quic_app_missing}} {ok, {skipped, quic_app_missing}}
end. end.
@ -278,54 +314,70 @@ delete_authentication(Type, ListenerName, _Conf) ->
%% Update the listeners at runtime %% Update the listeners at runtime
post_config_update(_, _Req, NewListeners, OldListeners, _AppEnvs) -> post_config_update(_, _Req, NewListeners, OldListeners, _AppEnvs) ->
#{added := Added, removed := Removed, changed := Updated} #{added := Added, removed := Removed, changed := Updated} =
= diff_listeners(NewListeners, OldListeners), diff_listeners(NewListeners, OldListeners),
perform_listener_changes(fun stop_listener/3, Removed), perform_listener_changes(fun stop_listener/3, Removed),
perform_listener_changes(fun delete_authentication/3, Removed), perform_listener_changes(fun delete_authentication/3, Removed),
perform_listener_changes(fun start_listener/3, Added), perform_listener_changes(fun start_listener/3, Added),
perform_listener_changes(fun restart_listener/3, Updated). perform_listener_changes(fun restart_listener/3, Updated).
perform_listener_changes(Action, MapConfs) -> perform_listener_changes(Action, MapConfs) ->
lists:foreach(fun lists:foreach(
({Id, Conf}) -> fun({Id, Conf}) ->
{Type, Name} = parse_listener_id(Id), {Type, Name} = parse_listener_id(Id),
Action(Type, Name, Conf) Action(Type, Name, Conf)
end, maps:to_list(MapConfs)). end,
maps:to_list(MapConfs)
).
diff_listeners(NewListeners, OldListeners) -> diff_listeners(NewListeners, OldListeners) ->
emqx_map_lib:diff_maps(flatten_listeners(NewListeners), flatten_listeners(OldListeners)). emqx_map_lib:diff_maps(flatten_listeners(NewListeners), flatten_listeners(OldListeners)).
flatten_listeners(Conf0) -> flatten_listeners(Conf0) ->
maps:from_list( maps:from_list(
lists:append([do_flatten_listeners(Type, Conf) lists:append([
|| {Type, Conf} <- maps:to_list(Conf0)])). do_flatten_listeners(Type, Conf)
|| {Type, Conf} <- maps:to_list(Conf0)
])
).
do_flatten_listeners(Type, Conf0) -> do_flatten_listeners(Type, Conf0) ->
[{listener_id(Type, Name), maps:remove(authentication, Conf)} || [
{Name, Conf} <- maps:to_list(Conf0)]. {listener_id(Type, Name), maps:remove(authentication, Conf)}
|| {Name, Conf} <- maps:to_list(Conf0)
].
esockd_opts(Type, Opts0) -> esockd_opts(Type, Opts0) ->
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0), Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
Limiter = limiter(Opts0), Limiter = limiter(Opts0),
Opts2 = case maps:get(connection, Limiter, undefined) of Opts2 =
undefined -> case maps:get(connection, Limiter, undefined) of
Opts1; undefined ->
BucketName -> Opts1;
Opts1#{limiter => emqx_esockd_htb_limiter:new_create_options(connection, BucketName)} BucketName ->
end, Opts1#{
Opts3 = Opts2#{ access_rules => esockd_access_rules(maps:get(access_rules, Opts0, [])) limiter => emqx_esockd_htb_limiter:new_create_options(connection, BucketName)
, tune_fun => {emqx_olp, backoff_new_conn, [zone(Opts0)]} }
}, end,
maps:to_list(case Type of Opts3 = Opts2#{
tcp -> Opts3#{tcp_options => tcp_opts(Opts0)}; access_rules => esockd_access_rules(maps:get(access_rules, Opts0, [])),
ssl -> Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)} tune_fun => {emqx_olp, backoff_new_conn, [zone(Opts0)]}
end). },
maps:to_list(
case Type of
tcp -> Opts3#{tcp_options => tcp_opts(Opts0)};
ssl -> Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)}
end
).
ws_opts(Type, ListenerName, Opts) -> ws_opts(Type, ListenerName, Opts) ->
WsPaths = [{maps:get(mqtt_path, Opts, "/mqtt"), emqx_ws_connection, WsPaths = [
#{zone => zone(Opts), {maps:get(mqtt_path, Opts, "/mqtt"), emqx_ws_connection, #{
listener => {Type, ListenerName}, zone => zone(Opts),
limiter => limiter(Opts)}}], listener => {Type, ListenerName},
limiter => limiter(Opts)
}}
],
Dispatch = cowboy_router:compile([{'_', WsPaths}]), Dispatch = cowboy_router:compile([{'_', WsPaths}]),
ProxyProto = maps:get(proxy_protocol, Opts, false), ProxyProto = maps:get(proxy_protocol, Opts, false),
#{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}. #{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}.
@ -333,16 +385,19 @@ ws_opts(Type, ListenerName, Opts) ->
ranch_opts(Type, ListenOn, Opts) -> ranch_opts(Type, ListenOn, Opts) ->
NumAcceptors = maps:get(acceptors, Opts, 4), NumAcceptors = maps:get(acceptors, Opts, 4),
MaxConnections = maps:get(max_connections, Opts, 1024), MaxConnections = maps:get(max_connections, Opts, 1024),
SocketOpts = case Type of SocketOpts =
wss -> tcp_opts(Opts) ++ proplists:delete(handshake_timeout, ssl_opts(Opts)); case Type of
ws -> tcp_opts(Opts) wss -> tcp_opts(Opts) ++ proplists:delete(handshake_timeout, ssl_opts(Opts));
end, ws -> tcp_opts(Opts)
#{num_acceptors => NumAcceptors, end,
max_connections => MaxConnections, #{
handshake_timeout => maps:get(handshake_timeout, Opts, 15000), num_acceptors => NumAcceptors,
socket_opts => ip_port(ListenOn) ++ max_connections => MaxConnections,
handshake_timeout => maps:get(handshake_timeout, Opts, 15000),
socket_opts => ip_port(ListenOn) ++
%% cowboy don't allow us to set 'reuseaddr' %% cowboy don't allow us to set 'reuseaddr'
proplists:delete(reuseaddr, SocketOpts)}. proplists:delete(reuseaddr, SocketOpts)
}.
ip_port(Port) when is_integer(Port) -> ip_port(Port) when is_integer(Port) ->
[{port, Port}]; [{port, Port}];
@ -355,7 +410,13 @@ port({_Addr, Port}) when is_integer(Port) -> Port.
esockd_access_rules(StrRules) -> esockd_access_rules(StrRules) ->
Access = fun(S) -> Access = fun(S) ->
[A, CIDR] = string:tokens(S, " "), [A, CIDR] = string:tokens(S, " "),
{list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end} {
list_to_atom(A),
case CIDR of
"all" -> all;
_ -> CIDR
end
}
end, end,
[Access(R) || R <- StrRules]. [Access(R) || R <- StrRules].
@ -389,7 +450,8 @@ parse_listener_id(Id) ->
true -> {list_to_existing_atom(Type), list_to_atom(Name)}; true -> {list_to_existing_atom(Type), list_to_atom(Name)};
false -> {error, {invalid_listener_id, Id}} false -> {error, {invalid_listener_id, Id}}
end; end;
_ -> {error, {invalid_listener_id, Id}} _ ->
{error, {invalid_listener_id, Id}}
end. end.
zone(Opts) -> zone(Opts) ->
@ -401,25 +463,36 @@ limiter(Opts) ->
ssl_opts(Opts) -> ssl_opts(Opts) ->
maps:to_list( maps:to_list(
emqx_tls_lib:drop_tls13_for_old_otp( emqx_tls_lib:drop_tls13_for_old_otp(
maps:without([enable], maps:without(
maps:get(ssl, Opts, #{})))). [enable],
maps:get(ssl, Opts, #{})
)
)
).
tcp_opts(Opts) -> tcp_opts(Opts) ->
maps:to_list( maps:to_list(
maps:without([active_n], maps:without(
maps:get(tcp, Opts, #{}))). [active_n],
maps:get(tcp, Opts, #{})
)
).
foreach_listeners(Do) -> foreach_listeners(Do) ->
lists:foreach( lists:foreach(
fun({Type, LName, LConf}) -> fun({Type, LName, LConf}) ->
Do(Type, LName, LConf) Do(Type, LName, LConf)
end, do_list()). end,
do_list()
).
has_enabled_listener_conf_by_type(Type) -> has_enabled_listener_conf_by_type(Type) ->
lists:any( lists:any(
fun({Type0, _LName, LConf}) when is_map(LConf) -> fun({Type0, _LName, LConf}) when is_map(LConf) ->
Type =:= Type0 andalso maps:get(enabled, LConf, true) Type =:= Type0 andalso maps:get(enabled, LConf, true)
end, do_list()). end,
do_list()
).
apply_on_listener(ListenerId, Do) -> apply_on_listener(ListenerId, Do) ->
{Type, ListenerName} = parse_listener_id(ListenerId), {Type, ListenerName} = parse_listener_id(ListenerId),

View File

@ -21,165 +21,169 @@
-elvis([{elvis_style, god_modules, disable}]). -elvis([{elvis_style, god_modules, disable}]).
%% Logs %% Logs
-export([ debug/1 -export([
, debug/2 debug/1,
, debug/3 debug/2,
, info/1 debug/3,
, info/2 info/1,
, info/3 info/2,
, warning/1 info/3,
, warning/2 warning/1,
, warning/3 warning/2,
, error/1 warning/3,
, error/2 error/1,
, error/3 error/2,
, critical/1 error/3,
, critical/2 critical/1,
, critical/3 critical/2,
]). critical/3
]).
%% Configs %% Configs
-export([ set_metadata_peername/1 -export([
, set_metadata_clientid/1 set_metadata_peername/1,
, set_proc_metadata/1 set_metadata_clientid/1,
, set_primary_log_level/1 set_proc_metadata/1,
, set_log_handler_level/2 set_primary_log_level/1,
, set_log_level/1 set_log_handler_level/2,
, set_all_log_handlers_level/1 set_log_level/1,
]). set_all_log_handlers_level/1
]).
-export([ get_primary_log_level/0 -export([
, tune_primary_log_level/0 get_primary_log_level/0,
, get_log_handlers/0 tune_primary_log_level/0,
, get_log_handlers/1 get_log_handlers/0,
, get_log_handler/1 get_log_handlers/1,
]). get_log_handler/1
]).
-export([ start_log_handler/1 -export([
, stop_log_handler/1 start_log_handler/1,
]). stop_log_handler/1
]).
-type(peername_str() :: list()). -type peername_str() :: list().
-type(logger_dst() :: file:filename() | console | unknown). -type logger_dst() :: file:filename() | console | unknown.
-type(logger_handler_info() :: #{ -type logger_handler_info() :: #{
id := logger:handler_id(), id := logger:handler_id(),
level := logger:level(), level := logger:level(),
dst := logger_dst(), dst := logger_dst(),
filters := [{logger:filter_id(), logger:filter()}], filters := [{logger:filter_id(), logger:filter()}],
status := started | stopped status := started | stopped
}). }.
-define(STOPPED_HANDLERS, {?MODULE, stopped_handlers}). -define(STOPPED_HANDLERS, {?MODULE, stopped_handlers}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(debug(unicode:chardata()) -> ok). -spec debug(unicode:chardata()) -> ok.
debug(Msg) -> debug(Msg) ->
logger:debug(Msg). logger:debug(Msg).
-spec(debug(io:format(), [term()]) -> ok). -spec debug(io:format(), [term()]) -> ok.
debug(Format, Args) -> debug(Format, Args) ->
logger:debug(Format, Args). logger:debug(Format, Args).
-spec(debug(logger:metadata(), io:format(), [term()]) -> ok). -spec debug(logger:metadata(), io:format(), [term()]) -> ok.
debug(Metadata, Format, Args) when is_map(Metadata) -> debug(Metadata, Format, Args) when is_map(Metadata) ->
logger:debug(Format, Args, Metadata). logger:debug(Format, Args, Metadata).
-spec info(unicode:chardata()) -> ok.
-spec(info(unicode:chardata()) -> ok).
info(Msg) -> info(Msg) ->
logger:info(Msg). logger:info(Msg).
-spec(info(io:format(), [term()]) -> ok). -spec info(io:format(), [term()]) -> ok.
info(Format, Args) -> info(Format, Args) ->
logger:info(Format, Args). logger:info(Format, Args).
-spec(info(logger:metadata(), io:format(), [term()]) -> ok). -spec info(logger:metadata(), io:format(), [term()]) -> ok.
info(Metadata, Format, Args) when is_map(Metadata) -> info(Metadata, Format, Args) when is_map(Metadata) ->
logger:info(Format, Args, Metadata). logger:info(Format, Args, Metadata).
-spec warning(unicode:chardata()) -> ok.
-spec(warning(unicode:chardata()) -> ok).
warning(Msg) -> warning(Msg) ->
logger:warning(Msg). logger:warning(Msg).
-spec(warning(io:format(), [term()]) -> ok). -spec warning(io:format(), [term()]) -> ok.
warning(Format, Args) -> warning(Format, Args) ->
logger:warning(Format, Args). logger:warning(Format, Args).
-spec(warning(logger:metadata(), io:format(), [term()]) -> ok). -spec warning(logger:metadata(), io:format(), [term()]) -> ok.
warning(Metadata, Format, Args) when is_map(Metadata) -> warning(Metadata, Format, Args) when is_map(Metadata) ->
logger:warning(Format, Args, Metadata). logger:warning(Format, Args, Metadata).
-spec error(unicode:chardata()) -> ok.
-spec(error(unicode:chardata()) -> ok).
error(Msg) -> error(Msg) ->
logger:error(Msg). logger:error(Msg).
-spec(error(io:format(), [term()]) -> ok). -spec error(io:format(), [term()]) -> ok.
error(Format, Args) -> error(Format, Args) ->
logger:error(Format, Args). logger:error(Format, Args).
-spec(error(logger:metadata(), io:format(), [term()]) -> ok). -spec error(logger:metadata(), io:format(), [term()]) -> ok.
error(Metadata, Format, Args) when is_map(Metadata) -> error(Metadata, Format, Args) when is_map(Metadata) ->
logger:error(Format, Args, Metadata). logger:error(Format, Args, Metadata).
-spec critical(unicode:chardata()) -> ok.
-spec(critical(unicode:chardata()) -> ok).
critical(Msg) -> critical(Msg) ->
logger:critical(Msg). logger:critical(Msg).
-spec(critical(io:format(), [term()]) -> ok). -spec critical(io:format(), [term()]) -> ok.
critical(Format, Args) -> critical(Format, Args) ->
logger:critical(Format, Args). logger:critical(Format, Args).
-spec(critical(logger:metadata(), io:format(), [term()]) -> ok). -spec critical(logger:metadata(), io:format(), [term()]) -> ok.
critical(Metadata, Format, Args) when is_map(Metadata) -> critical(Metadata, Format, Args) when is_map(Metadata) ->
logger:critical(Format, Args, Metadata). logger:critical(Format, Args, Metadata).
-spec(set_metadata_clientid(emqx_types:clientid()) -> ok). -spec set_metadata_clientid(emqx_types:clientid()) -> ok.
set_metadata_clientid(<<>>) -> set_metadata_clientid(<<>>) ->
ok; ok;
set_metadata_clientid(ClientId) -> set_metadata_clientid(ClientId) ->
set_proc_metadata(#{clientid => ClientId}). set_proc_metadata(#{clientid => ClientId}).
-spec(set_metadata_peername(peername_str()) -> ok). -spec set_metadata_peername(peername_str()) -> ok.
set_metadata_peername(Peername) -> set_metadata_peername(Peername) ->
set_proc_metadata(#{peername => Peername}). set_proc_metadata(#{peername => Peername}).
-spec(set_proc_metadata(logger:metadata()) -> ok). -spec set_proc_metadata(logger:metadata()) -> ok.
set_proc_metadata(Meta) -> set_proc_metadata(Meta) ->
logger:update_process_metadata(Meta). logger:update_process_metadata(Meta).
-spec(get_primary_log_level() -> logger:level()). -spec get_primary_log_level() -> logger:level().
get_primary_log_level() -> get_primary_log_level() ->
#{level := Level} = logger:get_primary_config(), #{level := Level} = logger:get_primary_config(),
Level. Level.
-spec tune_primary_log_level() -> ok. -spec tune_primary_log_level() -> ok.
tune_primary_log_level() -> tune_primary_log_level() ->
LowestLevel = lists:foldl(fun(#{level := Level}, OldLevel) -> LowestLevel = lists:foldl(
fun(#{level := Level}, OldLevel) ->
case logger:compare_levels(Level, OldLevel) of case logger:compare_levels(Level, OldLevel) of
lt -> Level; lt -> Level;
_ -> OldLevel _ -> OldLevel
end end
end, get_primary_log_level(), get_log_handlers()), end,
get_primary_log_level(),
get_log_handlers()
),
set_primary_log_level(LowestLevel). set_primary_log_level(LowestLevel).
-spec(set_primary_log_level(logger:level()) -> ok | {error, term()}). -spec set_primary_log_level(logger:level()) -> ok | {error, term()}.
set_primary_log_level(Level) -> set_primary_log_level(Level) ->
logger:set_primary_config(level, Level). logger:set_primary_config(level, Level).
-spec(get_log_handlers() -> [logger_handler_info()]). -spec get_log_handlers() -> [logger_handler_info()].
get_log_handlers() -> get_log_handlers() ->
get_log_handlers(started) ++ get_log_handlers(stopped). get_log_handlers(started) ++ get_log_handlers(stopped).
-spec(get_log_handlers(started | stopped) -> [logger_handler_info()]). -spec get_log_handlers(started | stopped) -> [logger_handler_info()].
get_log_handlers(started) -> get_log_handlers(started) ->
[log_handler_info(Conf, started) || Conf <- logger:get_handler_config()]; [log_handler_info(Conf, started) || Conf <- logger:get_handler_config()];
get_log_handlers(stopped) -> get_log_handlers(stopped) ->
[log_handler_info(Conf, stopped) || Conf <- list_stopped_handler_config()]. [log_handler_info(Conf, stopped) || Conf <- list_stopped_handler_config()].
-spec(get_log_handler(logger:handler_id()) -> logger_handler_info()). -spec get_log_handler(logger:handler_id()) -> logger_handler_info().
get_log_handler(HandlerId) -> get_log_handler(HandlerId) ->
case logger:get_handler_config(HandlerId) of case logger:get_handler_config(HandlerId) of
{ok, Conf} -> {ok, Conf} ->
@ -191,13 +195,15 @@ get_log_handler(HandlerId) ->
end end
end. end.
-spec(start_log_handler(logger:handler_id()) -> ok | {error, term()}). -spec start_log_handler(logger:handler_id()) -> ok | {error, term()}.
start_log_handler(HandlerId) -> start_log_handler(HandlerId) ->
case lists:member(HandlerId, logger:get_handler_ids()) of case lists:member(HandlerId, logger:get_handler_ids()) of
true -> ok; true ->
ok;
false -> false ->
case read_stopped_handler_config(HandlerId) of case read_stopped_handler_config(HandlerId) of
error -> {error, {not_found, HandlerId}}; error ->
{error, {not_found, HandlerId}};
{ok, Conf = #{module := Mod}} -> {ok, Conf = #{module := Mod}} ->
case logger:add_handler(HandlerId, Mod, Conf) of case logger:add_handler(HandlerId, Mod, Conf) of
ok -> remove_stopped_handler_config(HandlerId); ok -> remove_stopped_handler_config(HandlerId);
@ -206,7 +212,7 @@ start_log_handler(HandlerId) ->
end end
end. end.
-spec(stop_log_handler(logger:handler_id()) -> ok | {error, term()}). -spec stop_log_handler(logger:handler_id()) -> ok | {error, term()}.
stop_log_handler(HandlerId) -> stop_log_handler(HandlerId) ->
case logger:get_handler_config(HandlerId) of case logger:get_handler_config(HandlerId) of
{ok, Conf} -> {ok, Conf} ->
@ -218,20 +224,20 @@ stop_log_handler(HandlerId) ->
{error, {not_started, HandlerId}} {error, {not_started, HandlerId}}
end. end.
-spec(set_log_handler_level(logger:handler_id(), logger:level()) -> ok | {error, term()}). -spec set_log_handler_level(logger:handler_id(), logger:level()) -> ok | {error, term()}.
set_log_handler_level(HandlerId, Level) -> set_log_handler_level(HandlerId, Level) ->
case logger:set_handler_config(HandlerId, level, Level) of case logger:set_handler_config(HandlerId, level, Level) of
ok -> ok; ok ->
ok;
{error, _} -> {error, _} ->
case read_stopped_handler_config(HandlerId) of case read_stopped_handler_config(HandlerId) of
error -> {error, {not_found, HandlerId}}; error -> {error, {not_found, HandlerId}};
{ok, Conf} -> {ok, Conf} -> save_stopped_handler_config(HandlerId, Conf#{level => Level})
save_stopped_handler_config(HandlerId, Conf#{level => Level})
end end
end. end.
%% @doc Set both the primary and all handlers level in one command %% @doc Set both the primary and all handlers level in one command
-spec(set_log_level(logger:handler_id()) -> ok | {error, term()}). -spec set_log_level(logger:handler_id()) -> ok | {error, term()}.
set_log_level(Level) -> set_log_level(Level) ->
case set_primary_log_level(Level) of case set_primary_log_level(Level) of
ok -> set_all_log_handlers_level(Level); ok -> set_all_log_handlers_level(Level);
@ -242,18 +248,47 @@ set_log_level(Level) ->
%% Internal Functions %% Internal Functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
log_handler_info(#{id := Id, level := Level, module := logger_std_h, log_handler_info(
filters := Filters, config := #{type := Type}}, Status) when #{
Type =:= standard_io; id := Id,
Type =:= standard_error -> level := Level,
module := logger_std_h,
filters := Filters,
config := #{type := Type}
},
Status
) when
Type =:= standard_io;
Type =:= standard_error
->
#{id => Id, level => Level, dst => console, status => Status, filters => Filters}; #{id => Id, level => Level, dst => console, status => Status, filters => Filters};
log_handler_info(#{id := Id, level := Level, module := logger_std_h, log_handler_info(
filters := Filters, config := Config = #{type := file}}, Status) -> #{
#{id => Id, level => Level, status => Status, filters => Filters, id := Id,
dst => maps:get(file, Config, atom_to_list(Id))}; level := Level,
module := logger_std_h,
log_handler_info(#{id := Id, level := Level, module := logger_disk_log_h, filters := Filters,
filters := Filters, config := #{file := Filename}}, Status) -> config := Config = #{type := file}
},
Status
) ->
#{
id => Id,
level => Level,
status => Status,
filters => Filters,
dst => maps:get(file, Config, atom_to_list(Id))
};
log_handler_info(
#{
id := Id,
level := Level,
module := logger_disk_log_h,
filters := Filters,
config := #{file := Filename}
},
Status
) ->
#{id => Id, level => Level, dst => Filename, status => Status, filters => Filters}; #{id => Id, level => Level, dst => Filename, status => Status, filters => Filters};
log_handler_info(#{id := Id, level := Level, filters := Filters}, Status) -> log_handler_info(#{id := Id, level := Level, filters := Filters}, Status) ->
#{id => Id, level => Level, dst => unknown, status => Status, filters => Filters}. #{id => Id, level => Level, dst => unknown, status => Status, filters => Filters}.
@ -264,7 +299,8 @@ set_all_log_handlers_level(Level) ->
set_all_log_handlers_level([#{id := ID, level := Level} | List], NewLevel, ChangeHistory) -> set_all_log_handlers_level([#{id := ID, level := Level} | List], NewLevel, ChangeHistory) ->
case set_log_handler_level(ID, NewLevel) of case set_log_handler_level(ID, NewLevel) of
ok -> set_all_log_handlers_level(List, NewLevel, [{ID, Level} | ChangeHistory]); ok ->
set_all_log_handlers_level(List, NewLevel, [{ID, Level} | ChangeHistory]);
{error, Error} -> {error, Error} ->
rollback(ChangeHistory), rollback(ChangeHistory),
{error, {handlers_logger_level, {ID, Error}}} {error, {handlers_logger_level, {ID, Error}}}
@ -275,7 +311,8 @@ set_all_log_handlers_level([], _NewLevel, _NewHanlder) ->
rollback([{ID, Level} | List]) -> rollback([{ID, Level} | List]) ->
_ = set_log_handler_level(ID, Level), _ = set_log_handler_level(ID, Level),
rollback(List); rollback(List);
rollback([]) -> ok. rollback([]) ->
ok.
save_stopped_handler_config(HandlerId, Config) -> save_stopped_handler_config(HandlerId, Config) ->
case persistent_term:get(?STOPPED_HANDLERS, undefined) of case persistent_term:get(?STOPPED_HANDLERS, undefined) of
@ -291,12 +328,12 @@ read_stopped_handler_config(HandlerId) ->
end. end.
remove_stopped_handler_config(HandlerId) -> remove_stopped_handler_config(HandlerId) ->
case persistent_term:get(?STOPPED_HANDLERS, undefined) of case persistent_term:get(?STOPPED_HANDLERS, undefined) of
undefined -> ok; undefined ->
ok;
ConfList -> ConfList ->
case maps:find(HandlerId, ConfList) of case maps:find(HandlerId, ConfList) of
error -> ok; error -> ok;
{ok, _} -> {ok, _} -> persistent_term:put(?STOPPED_HANDLERS, maps:remove(HandlerId, ConfList))
persistent_term:put(?STOPPED_HANDLERS, maps:remove(HandlerId, ConfList))
end end
end. end.
list_stopped_handler_config() -> list_stopped_handler_config() ->

View File

@ -43,14 +43,16 @@
-export_type([config/0]). -export_type([config/0]).
-elvis([{elvis_style, no_nested_try_catch, #{ ignore => [emqx_logger_jsonfmt]}}]). -elvis([{elvis_style, no_nested_try_catch, #{ignore => [emqx_logger_jsonfmt]}}]).
%% this is what used when calling logger:log(Level, Report, Meta). %% this is what used when calling logger:log(Level, Report, Meta).
-define(DEFAULT_FORMATTER, fun logger:format_otp_report/1). -define(DEFAULT_FORMATTER, fun logger:format_otp_report/1).
-type config() :: #{depth => pos_integer() | unlimited, -type config() :: #{
report_cb => logger:report_cb(), depth => pos_integer() | unlimited,
single_line => boolean()}. report_cb => logger:report_cb(),
single_line => boolean()
}.
-define(IS_STRING(String), (is_list(String) orelse is_binary(String))). -define(IS_STRING(String), (is_list(String) orelse is_binary(String))).
@ -67,7 +69,7 @@ best_effort_json(Input) ->
-spec format(logger:log_event(), config()) -> iodata(). -spec format(logger:log_event(), config()) -> iodata().
format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) -> format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) ->
Config = add_default_config(Config0), Config = add_default_config(Config0),
[format(Msg, Meta#{level => Level}, Config) , "\n"]. [format(Msg, Meta#{level => Level}, Config), "\n"].
format(Msg, Meta, Config) -> format(Msg, Meta, Config) ->
Data0 = Data0 =
@ -78,12 +80,13 @@ format(Msg, Meta, Config) ->
Meta#{msg => Bin} Meta#{msg => Bin}
catch catch
C:R:S -> C:R:S ->
Meta#{ msg => "emqx_logger_jsonfmt_format_error" Meta#{
, fmt_raw_input => Msg msg => "emqx_logger_jsonfmt_format_error",
, fmt_error => C fmt_raw_input => Msg,
, fmt_reason => R fmt_error => C,
, fmt_stacktrace => S fmt_reason => R,
} fmt_stacktrace => S
}
end, end,
Data = maps:without([report_cb], Data0), Data = maps:without([report_cb], Data0),
jiffy:encode(json_obj(Data, Config)). jiffy:encode(json_obj(Data, Config)).
@ -102,8 +105,9 @@ maybe_format_msg(Msg, Meta, Config) ->
format_msg({string, Chardata}, Meta, Config) -> format_msg({string, Chardata}, Meta, Config) ->
%% already formatted %% already formatted
format_msg({"~ts", [Chardata]}, Meta, Config); format_msg({"~ts", [Chardata]}, Meta, Config);
format_msg({report, _} = Msg, Meta, #{report_cb := Fun} = Config) format_msg({report, _} = Msg, Meta, #{report_cb := Fun} = Config) when
when is_function(Fun,1); is_function(Fun,2) -> is_function(Fun, 1); is_function(Fun, 2)
->
%% a format callback function in config, no idea when this happens, but leaving it %% a format callback function in config, no idea when this happens, but leaving it
format_msg(Msg, Meta#{report_cb => Fun}, maps:remove(report_cb, Config)); format_msg(Msg, Meta#{report_cb => Fun}, maps:remove(report_cb, Config));
format_msg({report, Report}, #{report_cb := Fun} = Meta, Config) when is_function(Fun, 1) -> format_msg({report, Report}, #{report_cb := Fun} = Meta, Config) when is_function(Fun, 1) ->
@ -112,10 +116,11 @@ format_msg({report, Report}, #{report_cb := Fun} = Meta, Config) when is_functio
{Format, Args} when is_list(Format), is_list(Args) -> {Format, Args} when is_list(Format), is_list(Args) ->
format_msg({Format, Args}, maps:remove(report_cb, Meta), Config); format_msg({Format, Args}, maps:remove(report_cb, Meta), Config);
Other -> Other ->
#{ msg => "report_cb_bad_return" #{
, report_cb_fun => Fun msg => "report_cb_bad_return",
, report_cb_return => Other report_cb_fun => Fun,
} report_cb_return => Other
}
end; end;
format_msg({report, Report}, #{report_cb := Fun}, Config) when is_function(Fun, 2) -> format_msg({report, Report}, #{report_cb := Fun}, Config) when is_function(Fun, 2) ->
%% a format callback function of arity 2 %% a format callback function of arity 2
@ -125,30 +130,34 @@ format_msg({report, Report}, #{report_cb := Fun}, Config) when is_function(Fun,
unicode:characters_to_binary(Chardata, utf8) unicode:characters_to_binary(Chardata, utf8)
catch catch
_:_ -> _:_ ->
#{ msg => "report_cb_bad_return" #{
, report_cb_fun => Fun msg => "report_cb_bad_return",
, report_cb_return => Chardata report_cb_fun => Fun,
} report_cb_return => Chardata
}
end; end;
Other -> Other ->
#{ msg => "report_cb_bad_return" #{
, report_cb_fun => Fun msg => "report_cb_bad_return",
, report_cb_return => Other report_cb_fun => Fun,
} report_cb_return => Other
}
end; end;
format_msg({Fmt, Args}, _Meta, Config) -> format_msg({Fmt, Args}, _Meta, Config) ->
do_format_msg(Fmt, Args, Config). do_format_msg(Fmt, Args, Config).
do_format_msg(Format0, Args, #{depth := Depth, do_format_msg(Format0, Args, #{
single_line := SingleLine depth := Depth,
}) -> single_line := SingleLine
}) ->
Format1 = io_lib:scan_format(Format0, Args), Format1 = io_lib:scan_format(Format0, Args),
Format = reformat(Format1, Depth, SingleLine), Format = reformat(Format1, Depth, SingleLine),
Text0 = io_lib:build_text(Format, []), Text0 = io_lib:build_text(Format, []),
Text = case SingleLine of Text =
true -> re:replace(Text0, ",?\r?\n\s*",", ", [{return, list}, global, unicode]); case SingleLine of
false -> Text0 true -> re:replace(Text0, ",?\r?\n\s*", ", ", [{return, list}, global, unicode]);
end, false -> Text0
end,
trim(unicode:characters_to_binary(Text, utf8)). trim(unicode:characters_to_binary(Text, utf8)).
%% Get rid of the leading spaces. %% Get rid of the leading spaces.
@ -162,16 +171,18 @@ reformat([#{control_char := C} = M | T], Depth, true) when C =:= $p ->
[limit_depth(M#{width => 0}, Depth) | reformat(T, Depth, true)]; [limit_depth(M#{width => 0}, Depth) | reformat(T, Depth, true)];
reformat([#{control_char := C} = M | T], Depth, true) when C =:= $P -> reformat([#{control_char := C} = M | T], Depth, true) when C =:= $P ->
[M#{width => 0} | reformat(T, Depth, true)]; [M#{width => 0} | reformat(T, Depth, true)];
reformat([#{control_char := C}=M | T], Depth, Single) when C =:= $p; C =:= $w -> reformat([#{control_char := C} = M | T], Depth, Single) when C =:= $p; C =:= $w ->
[limit_depth(M, Depth) | reformat(T, Depth, Single)]; [limit_depth(M, Depth) | reformat(T, Depth, Single)];
reformat([H | T], Depth, Single) -> reformat([H | T], Depth, Single) ->
[H | reformat(T, Depth, Single)]; [H | reformat(T, Depth, Single)];
reformat([], _, _) -> reformat([], _, _) ->
[]. [].
limit_depth(M0, unlimited) -> M0; limit_depth(M0, unlimited) ->
limit_depth(#{control_char:=C0, args:=Args}=M0, Depth) -> M0;
C = C0 - ($a - $A), %To uppercase. limit_depth(#{control_char := C0, args := Args} = M0, Depth) ->
%To uppercase.
C = C0 - ($a - $A),
M0#{control_char := C, args := Args ++ [Depth]}. M0#{control_char := C, args := Args ++ [Depth]}.
add_default_config(Config0) -> add_default_config(Config0) ->
@ -187,7 +198,7 @@ best_effort_unicode(Input, Config) ->
B when is_binary(B) -> B; B when is_binary(B) -> B;
_ -> do_format_msg("~p", [Input], Config) _ -> do_format_msg("~p", [Input], Config)
catch catch
_ : _ -> _:_ ->
do_format_msg("~p", [Input], Config) do_format_msg("~p", [Input], Config)
end. end.
@ -195,19 +206,21 @@ best_effort_json_obj(List, Config) when is_list(List) ->
try try
json_obj(maps:from_list(List), Config) json_obj(maps:from_list(List), Config)
catch catch
_ : _ -> _:_ ->
[json(I, Config) || I <- List] [json(I, Config) || I <- List]
end; end;
best_effort_json_obj(Map, Config) -> best_effort_json_obj(Map, Config) ->
try try
json_obj(Map, Config) json_obj(Map, Config)
catch catch
_ : _ -> _:_ ->
do_format_msg("~p", [Map], Config) do_format_msg("~p", [Map], Config)
end. end.
json([], _) -> "[]"; json([], _) ->
json(<<"">>, _) -> "\"\""; "[]";
json(<<"">>, _) ->
"\"\"";
json(A, _) when is_atom(A) -> atom_to_binary(A, utf8); json(A, _) when is_atom(A) -> atom_to_binary(A, utf8);
json(I, _) when is_integer(I) -> I; json(I, _) when is_integer(I) -> I;
json(F, _) when is_float(F) -> F; json(F, _) when is_float(F) -> F;
@ -216,7 +229,7 @@ json(P, C) when is_port(P) -> json(port_to_list(P), C);
json(F, C) when is_function(F) -> json(erlang:fun_to_list(F), C); json(F, C) when is_function(F) -> json(erlang:fun_to_list(F), C);
json(B, Config) when is_binary(B) -> json(B, Config) when is_binary(B) ->
best_effort_unicode(B, Config); best_effort_unicode(B, Config);
json(L, Config) when is_list(L), is_integer(hd(L))-> json(L, Config) when is_list(L), is_integer(hd(L)) ->
best_effort_unicode(L, Config); best_effort_unicode(L, Config);
json(M, Config) when is_list(M), is_tuple(hd(M)), tuple_size(hd(M)) =:= 2 -> json(M, Config) when is_list(M), is_tuple(hd(M)), tuple_size(hd(M)) =:= 2 ->
best_effort_json_obj(M, Config); best_effort_json_obj(M, Config);
@ -228,15 +241,28 @@ json(Term, Config) ->
do_format_msg("~p", [Term], Config). do_format_msg("~p", [Term], Config).
json_obj(Data, Config) -> json_obj(Data, Config) ->
maps:fold(fun (K, V, D) -> maps:fold(
json_kv(K, V, D, Config) fun(K, V, D) ->
end, maps:new(), Data). json_kv(K, V, D, Config)
end,
maps:new(),
Data
).
json_kv(mfa, {M, F, A}, Data, _Config) -> json_kv(mfa, {M, F, A}, Data, _Config) ->
maps:put(mfa, <<(atom_to_binary(M, utf8))/binary, $:, maps:put(
(atom_to_binary(F, utf8))/binary, $/, mfa,
(integer_to_binary(A))/binary>>, Data); <<
json_kv('$kind', Kind, Data, Config) -> %% snabbkaffe (atom_to_binary(M, utf8))/binary,
$:,
(atom_to_binary(F, utf8))/binary,
$/,
(integer_to_binary(A))/binary
>>,
Data
);
%% snabbkaffe
json_kv('$kind', Kind, Data, Config) ->
maps:put(msg, json(Kind, Config), Data); maps:put(msg, json(Kind, Config), Data);
json_kv(gl, _, Data, _Config) -> json_kv(gl, _, Data, _Config) ->
%% drop gl because it's not interesting %% drop gl because it's not interesting
@ -267,44 +293,52 @@ json_key(Term) ->
no_crash_test_() -> no_crash_test_() ->
Opts = [{numtests, 1000}, {to_file, user}], Opts = [{numtests, 1000}, {to_file, user}],
{timeout, 30, {timeout, 30, fun() -> ?assert(proper:quickcheck(t_no_crash(), Opts)) end}.
fun() -> ?assert(proper:quickcheck(t_no_crash(), Opts)) end}.
t_no_crash() -> t_no_crash() ->
?FORALL({Level, Report, Meta, Config}, ?FORALL(
{p_level(), p_report(), p_meta(), p_config()}, {Level, Report, Meta, Config},
t_no_crash_run(Level, Report, Meta, Config)). {p_level(), p_report(), p_meta(), p_config()},
t_no_crash_run(Level, Report, Meta, Config)
).
t_no_crash_run(Level, Report, {undefined, Meta}, Config) -> t_no_crash_run(Level, Report, {undefined, Meta}, Config) ->
t_no_crash_run(Level, Report, maps:from_list(Meta), Config); t_no_crash_run(Level, Report, maps:from_list(Meta), Config);
t_no_crash_run(Level, Report, {ReportCb, Meta}, Config) -> t_no_crash_run(Level, Report, {ReportCb, Meta}, Config) ->
t_no_crash_run(Level, Report, maps:from_list([{report_cb, ReportCb} | Meta]), Config); t_no_crash_run(Level, Report, maps:from_list([{report_cb, ReportCb} | Meta]), Config);
t_no_crash_run(Level, Report, Meta, Config) -> t_no_crash_run(Level, Report, Meta, Config) ->
Input = #{ level => Level Input = #{
, msg => {report, Report} level => Level,
, meta => filter(Meta) msg => {report, Report},
}, meta => filter(Meta)
},
_ = format(Input, maps:from_list(Config)), _ = format(Input, maps:from_list(Config)),
true. true.
%% assume top level Report and Meta are sane %% assume top level Report and Meta are sane
filter(Map) -> filter(Map) ->
Keys = lists:filter( Keys = lists:filter(
fun(K) -> fun(K) ->
try json_key(K), true try
catch throw : {badkey, _} -> false json_key(K),
end true
end, maps:keys(Map)), catch
throw:{badkey, _} -> false
end
end,
maps:keys(Map)
),
maps:with(Keys, Map). maps:with(Keys, Map).
p_report_cb() -> p_report_cb() ->
proper_types:oneof([ fun ?MODULE:report_cb_1/1 proper_types:oneof([
, fun ?MODULE:report_cb_2/2 fun ?MODULE:report_cb_1/1,
, fun ?MODULE:report_cb_crash/2 fun ?MODULE:report_cb_2/2,
, fun logger:format_otp_report/1 fun ?MODULE:report_cb_crash/2,
, fun logger:format_report/1 fun logger:format_otp_report/1,
, format_report_undefined fun logger:format_report/1,
]). format_report_undefined
]).
report_cb_1(Input) -> {"~p", [Input]}. report_cb_1(Input) -> {"~p", [Input]}.
@ -314,9 +348,12 @@ report_cb_crash(_Input, _Config) -> error(report_cb_crash).
p_kvlist() -> p_kvlist() ->
proper_types:list({ proper_types:list({
proper_types:oneof([proper_types:atom(), proper_types:oneof([
proper_types:binary() proper_types:atom(),
]), proper_types:term()}). proper_types:binary()
]),
proper_types:term()
}).
%% meta type is 2-tuple, report_cb type, and some random key value pairs %% meta type is 2-tuple, report_cb type, and some random key value pairs
p_meta() -> p_meta() ->
@ -330,8 +367,10 @@ p_level() -> proper_types:oneof([info, debug, error, warning, foobar]).
p_config() -> p_config() ->
proper_types:shrink_list( proper_types:shrink_list(
[ {depth, p_limit()} [
, {single_line, proper_types:boolean()} {depth, p_limit()},
]). {single_line, proper_types:boolean()}
]
).
-endif. -endif.

View File

@ -44,8 +44,9 @@ try_format_unicode(Char) ->
{incomplete, _, _} -> error; {incomplete, _, _} -> error;
Binary -> Binary Binary -> Binary
end end
catch _:_ -> catch
error _:_ ->
error
end, end,
case List of case List of
error -> io_lib:format("~0p", [Char]); error -> io_lib:format("~0p", [Char]);
@ -54,15 +55,18 @@ try_format_unicode(Char) ->
enrich_report_mfa(Report, #{mfa := Mfa, line := Line}) -> enrich_report_mfa(Report, #{mfa := Mfa, line := Line}) ->
Report#{mfa => mfa(Mfa), line => Line}; Report#{mfa => mfa(Mfa), line => Line};
enrich_report_mfa(Report, _) -> Report. enrich_report_mfa(Report, _) ->
Report.
enrich_report_clientid(Report, #{clientid := ClientId}) -> enrich_report_clientid(Report, #{clientid := ClientId}) ->
Report#{clientid => try_format_unicode(ClientId)}; Report#{clientid => try_format_unicode(ClientId)};
enrich_report_clientid(Report, _) -> Report. enrich_report_clientid(Report, _) ->
Report.
enrich_report_peername(Report, #{peername := Peername}) -> enrich_report_peername(Report, #{peername := Peername}) ->
Report#{peername => Peername}; Report#{peername => Peername};
enrich_report_peername(Report, _) -> Report. enrich_report_peername(Report, _) ->
Report.
%% clientid and peername always in emqx_conn's process metadata. %% clientid and peername always in emqx_conn's process metadata.
%% topic can be put in meta using ?SLOG/3, or put in msg's report by ?SLOG/2 %% topic can be put in meta using ?SLOG/3, or put in msg's report by ?SLOG/2
@ -70,7 +74,8 @@ enrich_report_topic(Report, #{topic := Topic}) ->
Report#{topic => try_format_unicode(Topic)}; Report#{topic => try_format_unicode(Topic)};
enrich_report_topic(Report = #{topic := Topic}, _) -> enrich_report_topic(Report = #{topic := Topic}, _) ->
Report#{topic => try_format_unicode(Topic)}; Report#{topic => try_format_unicode(Topic)};
enrich_report_topic(Report, _) -> Report. enrich_report_topic(Report, _) ->
Report.
enrich_mfa({Fmt, Args}, #{mfa := Mfa, line := Line}) when is_list(Fmt) -> enrich_mfa({Fmt, Args}, #{mfa := Mfa, line := Line}) when is_list(Fmt) ->
{Fmt ++ " mfa: ~ts line: ~w", Args ++ [mfa(Mfa), Line]}; {Fmt ++ " mfa: ~ts line: ~w", Args ++ [mfa(Mfa), Line]};
@ -78,7 +83,7 @@ enrich_mfa(Msg, _) ->
Msg. Msg.
enrich_client_info({Fmt, Args}, #{clientid := ClientId, peername := Peer}) when is_list(Fmt) -> enrich_client_info({Fmt, Args}, #{clientid := ClientId, peername := Peer}) when is_list(Fmt) ->
{" ~ts@~ts " ++ Fmt, [ClientId, Peer | Args] }; {" ~ts@~ts " ++ Fmt, [ClientId, Peer | Args]};
enrich_client_info({Fmt, Args}, #{clientid := ClientId}) when is_list(Fmt) -> enrich_client_info({Fmt, Args}, #{clientid := ClientId}) when is_list(Fmt) ->
{" ~ts " ++ Fmt, [ClientId | Args]}; {" ~ts " ++ Fmt, [ClientId | Args]};
enrich_client_info({Fmt, Args}, #{peername := Peer}) when is_list(Fmt) -> enrich_client_info({Fmt, Args}, #{peername := Peer}) when is_list(Fmt) ->

View File

@ -15,26 +15,27 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_map_lib). -module(emqx_map_lib).
-export([ deep_get/2 -export([
, deep_get/3 deep_get/2,
, deep_find/2 deep_get/3,
, deep_put/3 deep_find/2,
, deep_remove/2 deep_put/3,
, deep_merge/2 deep_remove/2,
, safe_atom_key_map/1 deep_merge/2,
, unsafe_atom_key_map/1 safe_atom_key_map/1,
, jsonable_map/1 unsafe_atom_key_map/1,
, jsonable_map/2 jsonable_map/1,
, binary_string/1 jsonable_map/2,
, deep_convert/3 binary_string/1,
, diff_maps/2 deep_convert/3,
, merge_with/3 diff_maps/2,
]). merge_with/3
]).
-export_type([config_key/0, config_key_path/0]). -export_type([config_key/0, config_key_path/0]).
-type config_key() :: atom() | binary() | [byte()]. -type config_key() :: atom() | binary() | [byte()].
-type config_key_path() :: [config_key()]. -type config_key_path() :: [config_key()].
-type convert_fun() :: fun((...) -> {K1::any(), V1::any()} | drop). -type convert_fun() :: fun((...) -> {K1 :: any(), V1 :: any()} | drop).
%%----------------------------------------------------------------- %%-----------------------------------------------------------------
-spec deep_get(config_key_path(), map()) -> term(). -spec deep_get(config_key_path(), map()) -> term().
@ -81,8 +82,10 @@ deep_remove([Key | KeyPath], Map) ->
case maps:find(Key, Map) of case maps:find(Key, Map) of
{ok, SubMap} when is_map(SubMap) -> {ok, SubMap} when is_map(SubMap) ->
Map#{Key => deep_remove(KeyPath, SubMap)}; Map#{Key => deep_remove(KeyPath, SubMap)};
{ok, _Val} -> Map; {ok, _Val} ->
error -> Map Map;
error ->
Map
end. end.
%% #{a => #{b => 3, c => 2}, d => 4} %% #{a => #{b => 3, c => 2}, d => 4}
@ -90,7 +93,8 @@ deep_remove([Key | KeyPath], Map) ->
-spec deep_merge(map(), map()) -> map(). -spec deep_merge(map(), map()) -> map().
deep_merge(BaseMap, NewMap) -> deep_merge(BaseMap, NewMap) ->
NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap), NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap),
MergedBase = maps:fold(fun(K, V, Acc) -> MergedBase = maps:fold(
fun(K, V, Acc) ->
case maps:find(K, NewMap) of case maps:find(K, NewMap) of
error -> error ->
Acc#{K => V}; Acc#{K => V};
@ -99,20 +103,28 @@ deep_merge(BaseMap, NewMap) ->
{ok, NewV} -> {ok, NewV} ->
Acc#{K => NewV} Acc#{K => NewV}
end end
end, #{}, BaseMap), end,
#{},
BaseMap
),
maps:merge(MergedBase, maps:with(NewKeys, NewMap)). maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
-spec deep_convert(map(), convert_fun(), Args::list()) -> map(). -spec deep_convert(map(), convert_fun(), Args :: list()) -> map().
deep_convert(Map, ConvFun, Args) when is_map(Map) -> deep_convert(Map, ConvFun, Args) when is_map(Map) ->
maps:fold(fun(K, V, Acc) -> maps:fold(
fun(K, V, Acc) ->
case apply(ConvFun, [K, deep_convert(V, ConvFun, Args) | Args]) of case apply(ConvFun, [K, deep_convert(V, ConvFun, Args) | Args]) of
drop -> Acc; drop -> Acc;
{K1, V1} -> Acc#{K1 => V1} {K1, V1} -> Acc#{K1 => V1}
end end
end, #{}, Map); end,
#{},
Map
);
deep_convert(ListV, ConvFun, Args) when is_list(ListV) -> deep_convert(ListV, ConvFun, Args) when is_list(ListV) ->
[deep_convert(V, ConvFun, Args) || V <- ListV]; [deep_convert(V, ConvFun, Args) || V <- ListV];
deep_convert(Val, _, _Args) -> Val. deep_convert(Val, _, _Args) ->
Val.
-spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}. -spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
unsafe_atom_key_map(Map) -> unsafe_atom_key_map(Map) ->
@ -130,33 +142,41 @@ jsonable_map(Map, JsonableFun) ->
deep_convert(Map, fun binary_string_kv/3, [JsonableFun]). deep_convert(Map, fun binary_string_kv/3, [JsonableFun]).
-spec diff_maps(map(), map()) -> -spec diff_maps(map(), map()) ->
#{added := map(), identical := map(), removed := map(), #{
changed := #{any() => {OldValue::any(), NewValue::any()}}}. added := map(),
identical := map(),
removed := map(),
changed := #{any() => {OldValue :: any(), NewValue :: any()}}
}.
diff_maps(NewMap, OldMap) -> diff_maps(NewMap, OldMap) ->
InitR = #{identical => #{}, changed => #{}, removed => #{}}, InitR = #{identical => #{}, changed => #{}, removed => #{}},
{Result, RemInNew} = {Result, RemInNew} =
lists:foldl(fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D}, lists:foldl(
RemNewMap}) -> fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D}, RemNewMap}) ->
Result1 = case maps:find(OldK, NewMap) of Result1 =
error -> case maps:find(OldK, NewMap) of
Result0#{removed => D#{OldK => OldV}}; error ->
{ok, NewV} when NewV == OldV -> Result0#{removed => D#{OldK => OldV}};
Result0#{identical => I#{OldK => OldV}}; {ok, NewV} when NewV == OldV ->
{ok, NewV} -> Result0#{identical => I#{OldK => OldV}};
Result0#{changed => U#{OldK => {OldV, NewV}}} {ok, NewV} ->
Result0#{changed => U#{OldK => {OldV, NewV}}}
end,
{Result1, maps:remove(OldK, RemNewMap)}
end, end,
{Result1, maps:remove(OldK, RemNewMap)} {InitR, NewMap},
end, {InitR, NewMap}, maps:to_list(OldMap)), maps:to_list(OldMap)
),
Result#{added => RemInNew}. Result#{added => RemInNew}.
binary_string_kv(K, V, JsonableFun) -> binary_string_kv(K, V, JsonableFun) ->
case JsonableFun(K, V) of case JsonableFun(K, V) of
drop -> drop; drop -> drop;
{K1, V1} -> {binary_string(K1), V1} {K1, V1} -> {binary_string(K1), V1}
end. end.
binary_string([]) -> []; binary_string([]) ->
[];
binary_string(Val) when is_list(Val) -> binary_string(Val) when is_list(Val) ->
case io_lib:printable_unicode_list(Val) of case io_lib:printable_unicode_list(Val) of
true -> unicode:characters_to_binary(Val); true -> unicode:characters_to_binary(Val);
@ -167,40 +187,52 @@ binary_string(Val) ->
%%--------------------------------------------------------------------------- %%---------------------------------------------------------------------------
covert_keys_to_atom(BinKeyMap, Conv) -> covert_keys_to_atom(BinKeyMap, Conv) ->
deep_convert(BinKeyMap, fun deep_convert(
BinKeyMap,
fun
(K, V) when is_atom(K) -> {K, V}; (K, V) when is_atom(K) -> {K, V};
(K, V) when is_binary(K) -> {Conv(K), V} (K, V) when is_binary(K) -> {Conv(K), V}
end, []). end,
[]
).
%% copy from maps.erl OTP24.0 %% copy from maps.erl OTP24.0
-compile({inline, [error_with_info/2]}). -compile({inline, [error_with_info/2]}).
merge_with(Combiner, Map1, Map2) when is_map(Map1), merge_with(Combiner, Map1, Map2) when
is_map(Map2), is_map(Map1),
is_function(Combiner, 3) -> is_map(Map2),
is_function(Combiner, 3)
->
case map_size(Map1) > map_size(Map2) of case map_size(Map1) > map_size(Map2) of
true -> true ->
Iterator = maps:iterator(Map2), Iterator = maps:iterator(Map2),
merge_with_t(maps:next(Iterator), merge_with_t(
Map1, maps:next(Iterator),
Map2, Map1,
Combiner); Map2,
Combiner
);
false -> false ->
Iterator = maps:iterator(Map1), Iterator = maps:iterator(Map1),
merge_with_t(maps:next(Iterator), merge_with_t(
Map2, maps:next(Iterator),
Map1, Map2,
fun(K, V1, V2) -> Combiner(K, V2, V1) end) Map1,
fun(K, V1, V2) -> Combiner(K, V2, V1) end
)
end; end;
merge_with(Combiner, Map1, Map2) -> merge_with(Combiner, Map1, Map2) ->
error_with_info(error_type_merge_intersect(Map1, Map2, Combiner), error_with_info(
[Combiner, Map1, Map2]). error_type_merge_intersect(Map1, Map2, Combiner),
[Combiner, Map1, Map2]
).
merge_with_t({K, V2, Iterator}, Map1, Map2, Combiner) -> merge_with_t({K, V2, Iterator}, Map1, Map2, Combiner) ->
case Map1 of case Map1 of
#{ K := V1 } -> #{K := V1} ->
NewMap1 = Map1#{ K := Combiner(K, V1, V2) }, NewMap1 = Map1#{K := Combiner(K, V1, V2)},
merge_with_t(maps:next(Iterator), NewMap1, Map2, Combiner); merge_with_t(maps:next(Iterator), NewMap1, Map2, Combiner);
#{ } -> #{} ->
merge_with_t(maps:next(Iterator), maps:put(K, V2, Map1), Map2, Combiner) merge_with_t(maps:next(Iterator), maps:put(K, V2, Map1), Map2, Combiner)
end; end;
merge_with_t(none, Result, _, _) -> merge_with_t(none, Result, _, _) ->

View File

@ -23,309 +23,361 @@
-include("types.hrl"). -include("types.hrl").
%% Create %% Create
-export([ make/2 -export([
, make/3 make/2,
, make/4 make/3,
, make/6 make/4,
, make/7 make/6,
]). make/7
]).
%% Fields %% Fields
-export([ id/1 -export([
, qos/1 id/1,
, from/1 qos/1,
, topic/1 from/1,
, payload/1 topic/1,
, timestamp/1 payload/1,
]). timestamp/1
]).
%% Flags %% Flags
-export([ is_sys/1 -export([
, clean_dup/1 is_sys/1,
, get_flag/2 clean_dup/1,
, get_flag/3 get_flag/2,
, get_flags/1 get_flag/3,
, set_flag/2 get_flags/1,
, set_flag/3 set_flag/2,
, set_flags/2 set_flag/3,
, unset_flag/2 set_flags/2,
]). unset_flag/2
]).
%% Headers %% Headers
-export([ get_headers/1 -export([
, get_header/2 get_headers/1,
, get_header/3 get_header/2,
, set_header/3 get_header/3,
, set_headers/2 set_header/3,
, remove_header/2 set_headers/2,
]). remove_header/2
]).
-export([ is_expired/1 -export([
, update_expiry/1 is_expired/1,
]). update_expiry/1
]).
-export([ to_packet/2 -export([
, to_map/1 to_packet/2,
, to_log_map/1 to_map/1,
, to_list/1 to_log_map/1,
, from_map/1 to_list/1,
]). from_map/1
]).
-export_type([message_map/0]). -export_type([message_map/0]).
-type(message_map() :: #{id := binary(), -type message_map() :: #{
qos := 0 | 1 | 2, id := binary(),
from := atom() | binary(), qos := 0 | 1 | 2,
flags := emqx_types:flags(), from := atom() | binary(),
headers := emqx_types:headers(), flags := emqx_types:flags(),
topic := emqx_types:topic(), headers := emqx_types:headers(),
payload := emqx_types:payload(), topic := emqx_types:topic(),
timestamp := integer(), payload := emqx_types:payload(),
extra := _} timestamp := integer(),
). extra := _
}.
-elvis([{elvis_style, god_modules, disable}]). -elvis([{elvis_style, god_modules, disable}]).
-spec(make(emqx_types:topic(), emqx_types:payload()) -> emqx_types:message()). -spec make(emqx_types:topic(), emqx_types:payload()) -> emqx_types:message().
make(Topic, Payload) -> make(Topic, Payload) ->
make(undefined, Topic, Payload). make(undefined, Topic, Payload).
-spec(make(emqx_types:clientid(), -spec make(
emqx_types:topic(), emqx_types:clientid(),
emqx_types:payload()) -> emqx_types:message()). emqx_types:topic(),
emqx_types:payload()
) -> emqx_types:message().
make(From, Topic, Payload) -> make(From, Topic, Payload) ->
make(From, ?QOS_0, Topic, Payload). make(From, ?QOS_0, Topic, Payload).
-spec(make(emqx_types:clientid(), -spec make(
emqx_types:qos(), emqx_types:clientid(),
emqx_types:topic(), emqx_types:qos(),
emqx_types:payload()) -> emqx_types:message()). emqx_types:topic(),
emqx_types:payload()
) -> emqx_types:message().
make(From, QoS, Topic, Payload) when ?QOS_0 =< QoS, QoS =< ?QOS_2 -> make(From, QoS, Topic, Payload) when ?QOS_0 =< QoS, QoS =< ?QOS_2 ->
Now = erlang:system_time(millisecond), Now = erlang:system_time(millisecond),
#message{id = emqx_guid:gen(), #message{
qos = QoS, id = emqx_guid:gen(),
from = From, qos = QoS,
topic = Topic, from = From,
payload = Payload, topic = Topic,
timestamp = Now payload = Payload,
}. timestamp = Now
}.
-spec(make(emqx_types:clientid(), -spec make(
emqx_types:qos(), emqx_types:clientid(),
emqx_types:topic(), emqx_types:qos(),
emqx_types:payload(), emqx_types:topic(),
emqx_types:flags(), emqx_types:payload(),
emqx_types:headers()) -> emqx_types:message()). emqx_types:flags(),
make(From, QoS, Topic, Payload, Flags, Headers) emqx_types:headers()
when ?QOS_0 =< QoS, QoS =< ?QOS_2, ) -> emqx_types:message().
is_map(Flags), is_map(Headers) -> make(From, QoS, Topic, Payload, Flags, Headers) when
?QOS_0 =< QoS,
QoS =< ?QOS_2,
is_map(Flags),
is_map(Headers)
->
Now = erlang:system_time(millisecond), Now = erlang:system_time(millisecond),
#message{id = emqx_guid:gen(), #message{
qos = QoS, id = emqx_guid:gen(),
from = From, qos = QoS,
flags = Flags, from = From,
headers = Headers, flags = Flags,
topic = Topic, headers = Headers,
payload = Payload, topic = Topic,
timestamp = Now payload = Payload,
}. timestamp = Now
}.
-spec(make(MsgId :: binary(), -spec make(
emqx_types:clientid(), MsgId :: binary(),
emqx_types:qos(), emqx_types:clientid(),
emqx_types:topic(), emqx_types:qos(),
emqx_types:payload(), emqx_types:topic(),
emqx_types:flags(), emqx_types:payload(),
emqx_types:headers()) -> emqx_types:message()). emqx_types:flags(),
make(MsgId, From, QoS, Topic, Payload, Flags, Headers) emqx_types:headers()
when ?QOS_0 =< QoS, QoS =< ?QOS_2, ) -> emqx_types:message().
is_map(Flags), is_map(Headers) -> make(MsgId, From, QoS, Topic, Payload, Flags, Headers) when
?QOS_0 =< QoS,
QoS =< ?QOS_2,
is_map(Flags),
is_map(Headers)
->
Now = erlang:system_time(millisecond), Now = erlang:system_time(millisecond),
#message{id = MsgId, #message{
qos = QoS, id = MsgId,
from = From, qos = QoS,
flags = Flags, from = From,
headers = Headers, flags = Flags,
topic = Topic, headers = Headers,
payload = Payload, topic = Topic,
timestamp = Now payload = Payload,
}. timestamp = Now
}.
-spec(id(emqx_types:message()) -> maybe(binary())). -spec id(emqx_types:message()) -> maybe(binary()).
id(#message{id = Id}) -> Id. id(#message{id = Id}) -> Id.
-spec(qos(emqx_types:message()) -> emqx_types:qos()). -spec qos(emqx_types:message()) -> emqx_types:qos().
qos(#message{qos = QoS}) -> QoS. qos(#message{qos = QoS}) -> QoS.
-spec(from(emqx_types:message()) -> atom() | binary()). -spec from(emqx_types:message()) -> atom() | binary().
from(#message{from = From}) -> From. from(#message{from = From}) -> From.
-spec(topic(emqx_types:message()) -> emqx_types:topic()). -spec topic(emqx_types:message()) -> emqx_types:topic().
topic(#message{topic = Topic}) -> Topic. topic(#message{topic = Topic}) -> Topic.
-spec(payload(emqx_types:message()) -> emqx_types:payload()). -spec payload(emqx_types:message()) -> emqx_types:payload().
payload(#message{payload = Payload}) -> Payload. payload(#message{payload = Payload}) -> Payload.
-spec(timestamp(emqx_types:message()) -> integer()). -spec timestamp(emqx_types:message()) -> integer().
timestamp(#message{timestamp = TS}) -> TS. timestamp(#message{timestamp = TS}) -> TS.
-spec(is_sys(emqx_types:message()) -> boolean()). -spec is_sys(emqx_types:message()) -> boolean().
is_sys(#message{flags = #{sys := true}}) -> is_sys(#message{flags = #{sys := true}}) ->
true; true;
is_sys(#message{topic = <<"$SYS/", _/binary>>}) -> is_sys(#message{topic = <<"$SYS/", _/binary>>}) ->
true; true;
is_sys(_Msg) -> false. is_sys(_Msg) ->
false.
-spec(clean_dup(emqx_types:message()) -> emqx_types:message()). -spec clean_dup(emqx_types:message()) -> emqx_types:message().
clean_dup(Msg = #message{flags = Flags = #{dup := true}}) -> clean_dup(Msg = #message{flags = Flags = #{dup := true}}) ->
Msg#message{flags = Flags#{dup => false}}; Msg#message{flags = Flags#{dup => false}};
clean_dup(Msg) -> Msg. clean_dup(Msg) ->
Msg.
-spec(set_flags(map(), emqx_types:message()) -> emqx_types:message()). -spec set_flags(map(), emqx_types:message()) -> emqx_types:message().
set_flags(New, Msg = #message{flags = Old}) when is_map(New) -> set_flags(New, Msg = #message{flags = Old}) when is_map(New) ->
Msg#message{flags = maps:merge(Old, New)}. Msg#message{flags = maps:merge(Old, New)}.
-spec(get_flag(emqx_types:flag(), emqx_types:message()) -> boolean()). -spec get_flag(emqx_types:flag(), emqx_types:message()) -> boolean().
get_flag(Flag, Msg) -> get_flag(Flag, Msg) ->
get_flag(Flag, Msg, false). get_flag(Flag, Msg, false).
get_flag(Flag, #message{flags = Flags}, Default) -> get_flag(Flag, #message{flags = Flags}, Default) ->
maps:get(Flag, Flags, Default). maps:get(Flag, Flags, Default).
-spec(get_flags(emqx_types:message()) -> maybe(map())). -spec get_flags(emqx_types:message()) -> maybe(map()).
get_flags(#message{flags = Flags}) -> Flags. get_flags(#message{flags = Flags}) -> Flags.
-spec(set_flag(emqx_types:flag(), emqx_types:message()) -> emqx_types:message()). -spec set_flag(emqx_types:flag(), emqx_types:message()) -> emqx_types:message().
set_flag(Flag, Msg = #message{flags = Flags}) when is_atom(Flag) -> set_flag(Flag, Msg = #message{flags = Flags}) when is_atom(Flag) ->
Msg#message{flags = maps:put(Flag, true, Flags)}. Msg#message{flags = maps:put(Flag, true, Flags)}.
-spec(set_flag(emqx_types:flag(), boolean() | integer(), emqx_types:message()) -spec set_flag(emqx_types:flag(), boolean() | integer(), emqx_types:message()) ->
-> emqx_types:message()). emqx_types:message().
set_flag(Flag, Val, Msg = #message{flags = Flags}) when is_atom(Flag) -> set_flag(Flag, Val, Msg = #message{flags = Flags}) when is_atom(Flag) ->
Msg#message{flags = maps:put(Flag, Val, Flags)}. Msg#message{flags = maps:put(Flag, Val, Flags)}.
-spec(unset_flag(emqx_types:flag(), emqx_types:message()) -> emqx_types:message()). -spec unset_flag(emqx_types:flag(), emqx_types:message()) -> emqx_types:message().
unset_flag(Flag, Msg = #message{flags = Flags}) -> unset_flag(Flag, Msg = #message{flags = Flags}) ->
case maps:is_key(Flag, Flags) of case maps:is_key(Flag, Flags) of
true -> Msg#message{flags = maps:remove(Flag, Flags)}; true -> Msg#message{flags = maps:remove(Flag, Flags)};
false -> Msg false -> Msg
end. end.
-spec(set_headers(map(), emqx_types:message()) -> emqx_types:message()). -spec set_headers(map(), emqx_types:message()) -> emqx_types:message().
set_headers(New, Msg = #message{headers = Old}) when is_map(New) -> set_headers(New, Msg = #message{headers = Old}) when is_map(New) ->
Msg#message{headers = maps:merge(Old, New)}. Msg#message{headers = maps:merge(Old, New)}.
-spec(get_headers(emqx_types:message()) -> maybe(map())). -spec get_headers(emqx_types:message()) -> maybe(map()).
get_headers(Msg) -> Msg#message.headers. get_headers(Msg) -> Msg#message.headers.
-spec(get_header(term(), emqx_types:message()) -> term()). -spec get_header(term(), emqx_types:message()) -> term().
get_header(Hdr, Msg) -> get_header(Hdr, Msg) ->
get_header(Hdr, Msg, undefined). get_header(Hdr, Msg, undefined).
-spec(get_header(term(), emqx_types:message(), term()) -> term()). -spec get_header(term(), emqx_types:message(), term()) -> term().
get_header(Hdr, #message{headers = Headers}, Default) -> get_header(Hdr, #message{headers = Headers}, Default) ->
maps:get(Hdr, Headers, Default). maps:get(Hdr, Headers, Default).
-spec(set_header(term(), term(), emqx_types:message()) -> emqx_types:message()). -spec set_header(term(), term(), emqx_types:message()) -> emqx_types:message().
set_header(Hdr, Val, Msg = #message{headers = Headers}) -> set_header(Hdr, Val, Msg = #message{headers = Headers}) ->
Msg#message{headers = maps:put(Hdr, Val, Headers)}. Msg#message{headers = maps:put(Hdr, Val, Headers)}.
-spec(remove_header(term(), emqx_types:message()) -> emqx_types:message()). -spec remove_header(term(), emqx_types:message()) -> emqx_types:message().
remove_header(Hdr, Msg = #message{headers = Headers}) -> remove_header(Hdr, Msg = #message{headers = Headers}) ->
case maps:is_key(Hdr, Headers) of case maps:is_key(Hdr, Headers) of
true -> Msg#message{headers = maps:remove(Hdr, Headers)}; true -> Msg#message{headers = maps:remove(Hdr, Headers)};
false -> Msg false -> Msg
end. end.
-spec(is_expired(emqx_types:message()) -> boolean()). -spec is_expired(emqx_types:message()) -> boolean().
is_expired(#message{headers = #{properties := #{'Message-Expiry-Interval' := Interval}}, is_expired(#message{
timestamp = CreatedAt}) -> headers = #{properties := #{'Message-Expiry-Interval' := Interval}},
timestamp = CreatedAt
}) ->
elapsed(CreatedAt) > timer:seconds(Interval); elapsed(CreatedAt) > timer:seconds(Interval);
is_expired(_Msg) -> false. is_expired(_Msg) ->
false.
-spec(update_expiry(emqx_types:message()) -> emqx_types:message()). -spec update_expiry(emqx_types:message()) -> emqx_types:message().
update_expiry(Msg = #message{headers = #{properties := #{'Message-Expiry-Interval' := Interval}}, update_expiry(
timestamp = CreatedAt}) -> Msg = #message{
headers = #{properties := #{'Message-Expiry-Interval' := Interval}},
timestamp = CreatedAt
}
) ->
Props = maps:get(properties, Msg#message.headers), Props = maps:get(properties, Msg#message.headers),
case elapsed(CreatedAt) of case elapsed(CreatedAt) of
Elapsed when Elapsed > 0 -> Elapsed when Elapsed > 0 ->
Interval1 = max(1, Interval - (Elapsed div 1000)), Interval1 = max(1, Interval - (Elapsed div 1000)),
set_header(properties, Props#{'Message-Expiry-Interval' => Interval1}, Msg); set_header(properties, Props#{'Message-Expiry-Interval' => Interval1}, Msg);
_ -> Msg _ ->
Msg
end; end;
update_expiry(Msg) -> Msg. update_expiry(Msg) ->
Msg.
%% @doc Message to PUBLISH Packet. %% @doc Message to PUBLISH Packet.
-spec(to_packet(emqx_types:packet_id(), emqx_types:message()) -spec to_packet(emqx_types:packet_id(), emqx_types:message()) ->
-> emqx_types:packet()). emqx_types:packet().
to_packet(PacketId, Msg = #message{qos = QoS, headers = Headers, to_packet(
topic = Topic, payload = Payload}) -> PacketId,
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, Msg = #message{
dup = get_flag(dup, Msg), qos = QoS,
qos = QoS, headers = Headers,
retain = get_flag(retain, Msg) topic = Topic,
}, payload = Payload
variable = #mqtt_packet_publish{topic_name = Topic, }
packet_id = PacketId, ) ->
properties = filter_pub_props( #mqtt_packet{
maps:get(properties, Headers, #{})) header = #mqtt_packet_header{
}, type = ?PUBLISH,
payload = Payload dup = get_flag(dup, Msg),
}. qos = QoS,
retain = get_flag(retain, Msg)
},
variable = #mqtt_packet_publish{
topic_name = Topic,
packet_id = PacketId,
properties = filter_pub_props(
maps:get(properties, Headers, #{})
)
},
payload = Payload
}.
filter_pub_props(Props) -> filter_pub_props(Props) ->
maps:with(['Payload-Format-Indicator', maps:with(
'Message-Expiry-Interval', [
'Response-Topic', 'Payload-Format-Indicator',
'Correlation-Data', 'Message-Expiry-Interval',
'User-Property', 'Response-Topic',
'Subscription-Identifier', 'Correlation-Data',
'Content-Type' 'User-Property',
], Props). 'Subscription-Identifier',
'Content-Type'
],
Props
).
%% @doc Message to map %% @doc Message to map
-spec(to_map(emqx_types:message()) -> message_map()). -spec to_map(emqx_types:message()) -> message_map().
to_map(#message{ to_map(#message{
id = Id, id = Id,
qos = QoS, qos = QoS,
from = From, from = From,
flags = Flags, flags = Flags,
headers = Headers, headers = Headers,
topic = Topic, topic = Topic,
payload = Payload, payload = Payload,
timestamp = Timestamp, timestamp = Timestamp,
extra = Extra extra = Extra
}) -> }) ->
#{id => Id, #{
qos => QoS, id => Id,
from => From, qos => QoS,
flags => Flags, from => From,
headers => Headers, flags => Flags,
topic => Topic, headers => Headers,
payload => Payload, topic => Topic,
timestamp => Timestamp, payload => Payload,
extra => Extra timestamp => Timestamp,
}. extra => Extra
}.
%% @doc To map for logging, with payload dropped. %% @doc To map for logging, with payload dropped.
to_log_map(Msg) -> maps:without([payload], to_map(Msg)). to_log_map(Msg) -> maps:without([payload], to_map(Msg)).
%% @doc Message to tuple list %% @doc Message to tuple list
-spec(to_list(emqx_types:message()) -> list()). -spec to_list(emqx_types:message()) -> list().
to_list(Msg) -> to_list(Msg) ->
lists:zip(record_info(fields, message), tl(tuple_to_list(Msg))). lists:zip(record_info(fields, message), tl(tuple_to_list(Msg))).
%% @doc Map to message %% @doc Map to message
-spec(from_map(message_map()) -> emqx_types:message()). -spec from_map(message_map()) -> emqx_types:message().
from_map(#{id := Id, from_map(#{
qos := QoS, id := Id,
from := From, qos := QoS,
flags := Flags, from := From,
headers := Headers, flags := Flags,
topic := Topic, headers := Headers,
payload := Payload, topic := Topic,
timestamp := Timestamp, payload := Payload,
extra := Extra timestamp := Timestamp,
}) -> extra := Extra
}) ->
#message{ #message{
id = Id, id = Id,
qos = QoS, qos = QoS,

View File

@ -23,57 +23,62 @@
-include("types.hrl"). -include("types.hrl").
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-export([ start_link/0 -export([
, stop/0 start_link/0,
]). stop/0
]).
-export([ new/1 -export([
, new/2 new/1,
, ensure/1 new/2,
, ensure/2 ensure/1,
, all/0 ensure/2,
]). all/0
]).
-export([ val/1 -export([
, inc/1 val/1,
, inc/2 inc/1,
, dec/1 inc/2,
, dec/2 dec/1,
, set/2 dec/2,
]). set/2
]).
-export([ trans/2 -export([
, trans/3 trans/2,
, commit/0 trans/3,
]). commit/0
]).
%% Inc received/sent metrics %% Inc received/sent metrics
-export([ inc_msg/1 -export([
, inc_recv/1 inc_msg/1,
, inc_sent/1 inc_recv/1,
]). inc_sent/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
%% BACKW: v4.3.0 %% BACKW: v4.3.0
-export([ upgrade_retained_delayed_counter_type/0 -export([upgrade_retained_delayed_counter_type/0]).
]).
-export_type([metric_idx/0]). -export_type([metric_idx/0]).
-compile({inline, [inc/1, inc/2, dec/1, dec/2]}). -compile({inline, [inc/1, inc/2, dec/1, dec/2]}).
-compile({inline, [inc_recv/1, inc_sent/1]}). -compile({inline, [inc_recv/1, inc_sent/1]}).
-opaque(metric_idx() :: 1..1024). -opaque metric_idx() :: 1..1024.
-type(metric_name() :: atom() | string() | binary()). -type metric_name() :: atom() | string() | binary().
-define(MAX_SIZE, 1024). -define(MAX_SIZE, 1024).
-define(RESERVED_IDX, 512). -define(RESERVED_IDX, 512).
@ -82,133 +87,198 @@
%% Bytes sent and received %% Bytes sent and received
-define(BYTES_METRICS, -define(BYTES_METRICS,
[{counter, 'bytes.received'}, % Total bytes received % Total bytes received
{counter, 'bytes.sent'} % Total bytes sent [
]). {counter, 'bytes.received'},
% Total bytes sent
{counter, 'bytes.sent'}
]
).
%% Packets sent and received %% Packets sent and received
-define(PACKET_METRICS, -define(PACKET_METRICS,
[{counter, 'packets.received'}, % All Packets received % All Packets received
{counter, 'packets.sent'}, % All Packets sent [
{counter, 'packets.connect.received'}, % CONNECT Packets received {counter, 'packets.received'},
{counter, 'packets.connack.sent'}, % CONNACK Packets sent % All Packets sent
{counter, 'packets.connack.error'}, % CONNACK error sent {counter, 'packets.sent'},
{counter, 'packets.connack.auth_error'}, % CONNACK auth_error sent % CONNECT Packets received
{counter, 'packets.publish.received'}, % PUBLISH packets received {counter, 'packets.connect.received'},
{counter, 'packets.publish.sent'}, % PUBLISH packets sent % CONNACK Packets sent
{counter, 'packets.publish.inuse'}, % PUBLISH packet_id inuse {counter, 'packets.connack.sent'},
{counter, 'packets.publish.error'}, % PUBLISH failed for error % CONNACK error sent
{counter, 'packets.publish.auth_error'}, % PUBLISH failed for auth error {counter, 'packets.connack.error'},
{counter, 'packets.publish.dropped'}, % PUBLISH(QoS2) packets dropped % CONNACK auth_error sent
{counter, 'packets.puback.received'}, % PUBACK packets received {counter, 'packets.connack.auth_error'},
{counter, 'packets.puback.sent'}, % PUBACK packets sent % PUBLISH packets received
{counter, 'packets.puback.inuse'}, % PUBACK packet_id inuse {counter, 'packets.publish.received'},
{counter, 'packets.puback.missed'}, % PUBACK packets missed % PUBLISH packets sent
{counter, 'packets.pubrec.received'}, % PUBREC packets received {counter, 'packets.publish.sent'},
{counter, 'packets.pubrec.sent'}, % PUBREC packets sent % PUBLISH packet_id inuse
{counter, 'packets.pubrec.inuse'}, % PUBREC packet_id inuse {counter, 'packets.publish.inuse'},
{counter, 'packets.pubrec.missed'}, % PUBREC packets missed % PUBLISH failed for error
{counter, 'packets.pubrel.received'}, % PUBREL packets received {counter, 'packets.publish.error'},
{counter, 'packets.pubrel.sent'}, % PUBREL packets sent % PUBLISH failed for auth error
{counter, 'packets.pubrel.missed'}, % PUBREL packets missed {counter, 'packets.publish.auth_error'},
{counter, 'packets.pubcomp.received'}, % PUBCOMP packets received % PUBLISH(QoS2) packets dropped
{counter, 'packets.pubcomp.sent'}, % PUBCOMP packets sent {counter, 'packets.publish.dropped'},
{counter, 'packets.pubcomp.inuse'}, % PUBCOMP packet_id inuse % PUBACK packets received
{counter, 'packets.pubcomp.missed'}, % PUBCOMP packets missed {counter, 'packets.puback.received'},
{counter, 'packets.subscribe.received'}, % SUBSCRIBE Packets received % PUBACK packets sent
{counter, 'packets.subscribe.error'}, % SUBSCRIBE error {counter, 'packets.puback.sent'},
{counter, 'packets.subscribe.auth_error'}, % SUBSCRIBE failed for not auth % PUBACK packet_id inuse
{counter, 'packets.suback.sent'}, % SUBACK packets sent {counter, 'packets.puback.inuse'},
{counter, 'packets.unsubscribe.received'}, % UNSUBSCRIBE Packets received % PUBACK packets missed
{counter, 'packets.unsubscribe.error'}, % UNSUBSCRIBE error {counter, 'packets.puback.missed'},
{counter, 'packets.unsuback.sent'}, % UNSUBACK Packets sent % PUBREC packets received
{counter, 'packets.pingreq.received'}, % PINGREQ packets received {counter, 'packets.pubrec.received'},
{counter, 'packets.pingresp.sent'}, % PINGRESP Packets sent % PUBREC packets sent
{counter, 'packets.disconnect.received'}, % DISCONNECT Packets received {counter, 'packets.pubrec.sent'},
{counter, 'packets.disconnect.sent'}, % DISCONNECT Packets sent % PUBREC packet_id inuse
{counter, 'packets.auth.received'}, % Auth Packets received {counter, 'packets.pubrec.inuse'},
{counter, 'packets.auth.sent'} % Auth Packets sent % PUBREC packets missed
]). {counter, 'packets.pubrec.missed'},
% PUBREL packets received
{counter, 'packets.pubrel.received'},
% PUBREL packets sent
{counter, 'packets.pubrel.sent'},
% PUBREL packets missed
{counter, 'packets.pubrel.missed'},
% PUBCOMP packets received
{counter, 'packets.pubcomp.received'},
% PUBCOMP packets sent
{counter, 'packets.pubcomp.sent'},
% PUBCOMP packet_id inuse
{counter, 'packets.pubcomp.inuse'},
% PUBCOMP packets missed
{counter, 'packets.pubcomp.missed'},
% SUBSCRIBE Packets received
{counter, 'packets.subscribe.received'},
% SUBSCRIBE error
{counter, 'packets.subscribe.error'},
% SUBSCRIBE failed for not auth
{counter, 'packets.subscribe.auth_error'},
% SUBACK packets sent
{counter, 'packets.suback.sent'},
% UNSUBSCRIBE Packets received
{counter, 'packets.unsubscribe.received'},
% UNSUBSCRIBE error
{counter, 'packets.unsubscribe.error'},
% UNSUBACK Packets sent
{counter, 'packets.unsuback.sent'},
% PINGREQ packets received
{counter, 'packets.pingreq.received'},
% PINGRESP Packets sent
{counter, 'packets.pingresp.sent'},
% DISCONNECT Packets received
{counter, 'packets.disconnect.received'},
% DISCONNECT Packets sent
{counter, 'packets.disconnect.sent'},
% Auth Packets received
{counter, 'packets.auth.received'},
% Auth Packets sent
{counter, 'packets.auth.sent'}
]
).
%% Messages sent/received and pubsub %% Messages sent/received and pubsub
-define(MESSAGE_METRICS, -define(MESSAGE_METRICS,
[{counter, 'messages.received'}, % All Messages received % All Messages received
{counter, 'messages.sent'}, % All Messages sent [
{counter, 'messages.qos0.received'}, % QoS0 Messages received {counter, 'messages.received'},
{counter, 'messages.qos0.sent'}, % QoS0 Messages sent % All Messages sent
{counter, 'messages.qos1.received'}, % QoS1 Messages received {counter, 'messages.sent'},
{counter, 'messages.qos1.sent'}, % QoS1 Messages sent % QoS0 Messages received
{counter, 'messages.qos2.received'}, % QoS2 Messages received {counter, 'messages.qos0.received'},
{counter, 'messages.qos2.sent'}, % QoS2 Messages sent % QoS0 Messages sent
%% PubSub Metrics {counter, 'messages.qos0.sent'},
{counter, 'messages.publish'}, % Messages Publish % QoS1 Messages received
{counter, 'messages.dropped'}, % Messages dropped due to no subscribers {counter, 'messages.qos1.received'},
{counter, 'messages.dropped.await_pubrel_timeout'}, % QoS2 Messages expired % QoS1 Messages sent
{counter, 'messages.dropped.no_subscribers'}, % Messages dropped {counter, 'messages.qos1.sent'},
{counter, 'messages.forward'}, % Messages forward % QoS2 Messages received
{counter, 'messages.delayed'}, % Messages delayed {counter, 'messages.qos2.received'},
{counter, 'messages.delivered'}, % Messages delivered % QoS2 Messages sent
{counter, 'messages.acked'} % Messages acked {counter, 'messages.qos2.sent'},
]). %% PubSub Metrics
% Messages Publish
{counter, 'messages.publish'},
% Messages dropped due to no subscribers
{counter, 'messages.dropped'},
% QoS2 Messages expired
{counter, 'messages.dropped.await_pubrel_timeout'},
% Messages dropped
{counter, 'messages.dropped.no_subscribers'},
% Messages forward
{counter, 'messages.forward'},
% Messages delayed
{counter, 'messages.delayed'},
% Messages delivered
{counter, 'messages.delivered'},
% Messages acked
{counter, 'messages.acked'}
]
).
%% Delivery metrics %% Delivery metrics
-define(DELIVERY_METRICS, -define(DELIVERY_METRICS, [
[{counter, 'delivery.dropped'}, {counter, 'delivery.dropped'},
{counter, 'delivery.dropped.no_local'}, {counter, 'delivery.dropped.no_local'},
{counter, 'delivery.dropped.too_large'}, {counter, 'delivery.dropped.too_large'},
{counter, 'delivery.dropped.qos0_msg'}, {counter, 'delivery.dropped.qos0_msg'},
{counter, 'delivery.dropped.queue_full'}, {counter, 'delivery.dropped.queue_full'},
{counter, 'delivery.dropped.expired'} {counter, 'delivery.dropped.expired'}
]). ]).
%% Client Lifecircle metrics %% Client Lifecircle metrics
-define(CLIENT_METRICS, -define(CLIENT_METRICS, [
[{counter, 'client.connect'}, {counter, 'client.connect'},
{counter, 'client.connack'}, {counter, 'client.connack'},
{counter, 'client.connected'}, {counter, 'client.connected'},
{counter, 'client.authenticate'}, {counter, 'client.authenticate'},
{counter, 'client.auth.anonymous'}, {counter, 'client.auth.anonymous'},
{counter, 'client.authorize'}, {counter, 'client.authorize'},
{counter, 'client.subscribe'}, {counter, 'client.subscribe'},
{counter, 'client.unsubscribe'}, {counter, 'client.unsubscribe'},
{counter, 'client.disconnected'} {counter, 'client.disconnected'}
]). ]).
%% Session Lifecircle metrics %% Session Lifecircle metrics
-define(SESSION_METRICS, -define(SESSION_METRICS, [
[{counter, 'session.created'}, {counter, 'session.created'},
{counter, 'session.resumed'}, {counter, 'session.resumed'},
{counter, 'session.takenover'}, {counter, 'session.takenover'},
{counter, 'session.discarded'}, {counter, 'session.discarded'},
{counter, 'session.terminated'} {counter, 'session.terminated'}
]). ]).
%% Statistic metrics for ACL checking %% Statistic metrics for ACL checking
-define(STASTS_ACL_METRICS, -define(STASTS_ACL_METRICS, [
[ {counter, 'client.acl.allow'}, {counter, 'client.acl.allow'},
{counter, 'client.acl.deny'}, {counter, 'client.acl.deny'},
{counter, 'client.acl.cache_hit'} {counter, 'client.acl.cache_hit'}
]). ]).
%% Overload protetion counters %% Overload protetion counters
-define(OLP_METRICS, -define(OLP_METRICS, [
[{counter, 'olp.delay.ok'}, {counter, 'olp.delay.ok'},
{counter, 'olp.delay.timeout'}, {counter, 'olp.delay.timeout'},
{counter, 'olp.hbn'}, {counter, 'olp.hbn'},
{counter, 'olp.gc'}, {counter, 'olp.gc'},
{counter, 'olp.new_conn'} {counter, 'olp.new_conn'}
]). ]).
-record(state, {next_idx = 1}). -record(state, {next_idx = 1}).
-record(metric, {name, type, idx}). -record(metric, {name, type, idx}).
%% @doc Start the metrics server. %% @doc Start the metrics server.
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(stop() -> ok). -spec stop() -> ok.
stop() -> gen_server:stop(?SERVER). stop() -> gen_server:stop(?SERVER).
%% BACKW: v4.3.0 %% BACKW: v4.3.0
@ -220,21 +290,21 @@ upgrade_retained_delayed_counter_type() ->
%% Metrics API %% Metrics API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(new(metric_name()) -> ok). -spec new(metric_name()) -> ok.
new(Name) -> new(Name) ->
new(counter, Name). new(counter, Name).
-spec(new(gauge|counter, metric_name()) -> ok). -spec new(gauge | counter, metric_name()) -> ok.
new(gauge, Name) -> new(gauge, Name) ->
create(gauge, Name); create(gauge, Name);
new(counter, Name) -> new(counter, Name) ->
create(counter, Name). create(counter, Name).
-spec(ensure(metric_name()) -> ok). -spec ensure(metric_name()) -> ok.
ensure(Name) -> ensure(Name) ->
ensure(counter, Name). ensure(counter, Name).
-spec(ensure(gauge|counter, metric_name()) -> ok). -spec ensure(gauge | counter, metric_name()) -> ok.
ensure(Type, Name) when Type =:= gauge; Type =:= counter -> ensure(Type, Name) when Type =:= gauge; Type =:= counter ->
case ets:lookup(?TAB, Name) of case ets:lookup(?TAB, Name) of
[] -> create(Type, Name); [] -> create(Type, Name);
@ -249,73 +319,80 @@ create(Type, Name) ->
end. end.
%% @doc Get all metrics %% @doc Get all metrics
-spec(all() -> [{metric_name(), non_neg_integer()}]). -spec all() -> [{metric_name(), non_neg_integer()}].
all() -> all() ->
CRef = persistent_term:get(?MODULE), CRef = persistent_term:get(?MODULE),
[{Name, counters:get(CRef, Idx)} [
|| #metric{name = Name, idx = Idx} <- ets:tab2list(?TAB)]. {Name, counters:get(CRef, Idx)}
|| #metric{name = Name, idx = Idx} <- ets:tab2list(?TAB)
].
%% @doc Get metric value %% @doc Get metric value
-spec(val(metric_name()) -> maybe(non_neg_integer())). -spec val(metric_name()) -> maybe(non_neg_integer()).
val(Name) -> val(Name) ->
case ets:lookup(?TAB, Name) of case ets:lookup(?TAB, Name) of
[#metric{idx = Idx}] -> [#metric{idx = Idx}] ->
CRef = persistent_term:get(?MODULE), CRef = persistent_term:get(?MODULE),
counters:get(CRef, Idx); counters:get(CRef, Idx);
[] -> undefined [] ->
undefined
end. end.
%% @doc Increase counter %% @doc Increase counter
-spec(inc(metric_name()) -> ok). -spec inc(metric_name()) -> ok.
inc(Name) -> inc(Name) ->
inc(Name, 1). inc(Name, 1).
%% @doc Increase metric value %% @doc Increase metric value
-spec(inc(metric_name(), pos_integer()) -> ok). -spec inc(metric_name(), pos_integer()) -> ok.
inc(Name, Value) -> inc(Name, Value) ->
update_counter(Name, Value). update_counter(Name, Value).
%% @doc Decrease metric value %% @doc Decrease metric value
-spec(dec(metric_name()) -> ok). -spec dec(metric_name()) -> ok.
dec(Name) -> dec(Name) ->
dec(Name, 1). dec(Name, 1).
%% @doc Decrease metric value %% @doc Decrease metric value
-spec(dec(metric_name(), pos_integer()) -> ok). -spec dec(metric_name(), pos_integer()) -> ok.
dec(Name, Value) -> dec(Name, Value) ->
update_counter(Name, -Value). update_counter(Name, -Value).
%% @doc Set metric value %% @doc Set metric value
-spec(set(metric_name(), integer()) -> ok). -spec set(metric_name(), integer()) -> ok.
set(Name, Value) -> set(Name, Value) ->
CRef = persistent_term:get(?MODULE), CRef = persistent_term:get(?MODULE),
Idx = ets:lookup_element(?TAB, Name, 4), Idx = ets:lookup_element(?TAB, Name, 4),
counters:put(CRef, Idx, Value). counters:put(CRef, Idx, Value).
-spec(trans(inc | dec, metric_name()) -> ok). -spec trans(inc | dec, metric_name()) -> ok.
trans(Op, Name) when Op =:= inc; Op =:= dec -> trans(Op, Name) when Op =:= inc; Op =:= dec ->
trans(Op, Name, 1). trans(Op, Name, 1).
-spec(trans(inc | dec, metric_name(), pos_integer()) -> ok). -spec trans(inc | dec, metric_name(), pos_integer()) -> ok.
trans(inc, Name, Value) -> trans(inc, Name, Value) ->
cache(Name, Value); cache(Name, Value);
trans(dec, Name, Value) -> trans(dec, Name, Value) ->
cache(Name, -Value). cache(Name, -Value).
-spec(cache(metric_name(), integer()) -> ok). -spec cache(metric_name(), integer()) -> ok.
cache(Name, Value) -> cache(Name, Value) ->
put('$metrics', case get('$metrics') of put(
undefined -> '$metrics',
#{Name => Value}; case get('$metrics') of
Metrics -> undefined ->
maps:update_with(Name, fun(Cnt) -> Cnt + Value end, Value, Metrics) #{Name => Value};
end), Metrics ->
maps:update_with(Name, fun(Cnt) -> Cnt + Value end, Value, Metrics)
end
),
ok. ok.
-spec(commit() -> ok). -spec commit() -> ok.
commit() -> commit() ->
case get('$metrics') of case get('$metrics') of
undefined -> ok; undefined ->
ok;
Metrics -> Metrics ->
_ = erase('$metrics'), _ = erase('$metrics'),
lists:foreach(fun update_counter/1, maps:to_list(Metrics)) lists:foreach(fun update_counter/1, maps:to_list(Metrics))
@ -326,18 +403,18 @@ update_counter({Name, Value}) ->
update_counter(Name, Value) -> update_counter(Name, Value) ->
CRef = persistent_term:get(?MODULE), CRef = persistent_term:get(?MODULE),
CIdx = case reserved_idx(Name) of CIdx =
Idx when is_integer(Idx) -> Idx; case reserved_idx(Name) of
undefined -> Idx when is_integer(Idx) -> Idx;
ets:lookup_element(?TAB, Name, 4) undefined -> ets:lookup_element(?TAB, Name, 4)
end, end,
counters:add(CRef, CIdx, Value). counters:add(CRef, CIdx, Value).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Inc received/sent metrics %% Inc received/sent metrics
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(inc_msg(emqx_types:massage()) -> ok). -spec inc_msg(emqx_types:massage()) -> ok.
inc_msg(Msg) -> inc_msg(Msg) ->
case Msg#message.qos of case Msg#message.qos of
0 -> inc('messages.qos0.received'); 0 -> inc('messages.qos0.received');
@ -347,7 +424,7 @@ inc_msg(Msg) ->
inc('messages.received'). inc('messages.received').
%% @doc Inc packets received. %% @doc Inc packets received.
-spec(inc_recv(emqx_types:packet()) -> ok). -spec inc_recv(emqx_types:packet()) -> ok.
inc_recv(Packet) -> inc_recv(Packet) ->
inc('packets.received'), inc('packets.received'),
do_inc_recv(Packet). do_inc_recv(Packet).
@ -381,10 +458,11 @@ do_inc_recv(?PACKET(?DISCONNECT)) ->
inc('packets.disconnect.received'); inc('packets.disconnect.received');
do_inc_recv(?PACKET(?AUTH)) -> do_inc_recv(?PACKET(?AUTH)) ->
inc('packets.auth.received'); inc('packets.auth.received');
do_inc_recv(_Packet) -> ok. do_inc_recv(_Packet) ->
ok.
%% @doc Inc packets sent. Will not count $SYS PUBLISH. %% @doc Inc packets sent. Will not count $SYS PUBLISH.
-spec(inc_sent(emqx_types:packet()) -> ok). -spec inc_sent(emqx_types:packet()) -> ok.
inc_sent(?PUBLISH_PACKET(_QoS, <<"$SYS/", _/binary>>, _, _)) -> inc_sent(?PUBLISH_PACKET(_QoS, <<"$SYS/", _/binary>>, _, _)) ->
ok; ok;
inc_sent(Packet) -> inc_sent(Packet) ->
@ -396,7 +474,6 @@ do_inc_sent(?CONNACK_PACKET(ReasonCode)) ->
(ReasonCode == ?RC_NOT_AUTHORIZED) andalso inc('packets.connack.auth_error'), (ReasonCode == ?RC_NOT_AUTHORIZED) andalso inc('packets.connack.auth_error'),
(ReasonCode == ?RC_BAD_USER_NAME_OR_PASSWORD) andalso inc('packets.connack.auth_error'), (ReasonCode == ?RC_BAD_USER_NAME_OR_PASSWORD) andalso inc('packets.connack.auth_error'),
inc('packets.connack.sent'); inc('packets.connack.sent');
do_inc_sent(?PUBLISH_PACKET(QoS)) -> do_inc_sent(?PUBLISH_PACKET(QoS)) ->
inc('messages.sent'), inc('messages.sent'),
case QoS of case QoS of
@ -428,7 +505,8 @@ do_inc_sent(?PACKET(?DISCONNECT)) ->
inc('packets.disconnect.sent'); inc('packets.disconnect.sent');
do_inc_sent(?PACKET(?AUTH)) -> do_inc_sent(?PACKET(?AUTH)) ->
inc('packets.auth.sent'); inc('packets.auth.sent');
do_inc_sent(_Packet) -> ok. do_inc_sent(_Packet) ->
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
@ -440,22 +518,26 @@ init([]) ->
ok = persistent_term:put(?MODULE, CRef), ok = persistent_term:put(?MODULE, CRef),
% Create index mapping table % Create index mapping table
ok = emqx_tables:new(?TAB, [{keypos, 2}, {read_concurrency, true}]), ok = emqx_tables:new(?TAB, [{keypos, 2}, {read_concurrency, true}]),
Metrics = lists:append([?BYTES_METRICS, Metrics = lists:append([
?PACKET_METRICS, ?BYTES_METRICS,
?MESSAGE_METRICS, ?PACKET_METRICS,
?DELIVERY_METRICS, ?MESSAGE_METRICS,
?CLIENT_METRICS, ?DELIVERY_METRICS,
?SESSION_METRICS, ?CLIENT_METRICS,
?STASTS_ACL_METRICS, ?SESSION_METRICS,
?OLP_METRICS ?STASTS_ACL_METRICS,
]), ?OLP_METRICS
]),
% Store reserved indices % Store reserved indices
ok = lists:foreach(fun({Type, Name}) -> ok = lists:foreach(
Idx = reserved_idx(Name), fun({Type, Name}) ->
Metric = #metric{name = Name, type = Type, idx = Idx}, Idx = reserved_idx(Name),
true = ets:insert(?TAB, Metric), Metric = #metric{name = Name, type = Type, idx = Idx},
ok = counters:put(CRef, Idx, 0) true = ets:insert(?TAB, Metric),
end, Metrics), ok = counters:put(CRef, Idx, 0)
end,
Metrics
),
{ok, #state{next_idx = ?RESERVED_IDX + 1}, hibernate}. {ok, #state{next_idx = ?RESERVED_IDX + 1}, hibernate}.
handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) -> handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) ->
@ -465,7 +547,6 @@ handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) -
name => Name name => Name
}), }),
{reply, {error, metric_index_exceeded}, State}; {reply, {error, metric_index_exceeded}, State};
handle_call({create, Type, Name}, _From, State = #state{next_idx = NextIdx}) -> handle_call({create, Type, Name}, _From, State = #state{next_idx = NextIdx}) ->
case ets:lookup(?TAB, Name) of case ets:lookup(?TAB, Name) of
[#metric{idx = Idx}] -> [#metric{idx = Idx}] ->
@ -476,14 +557,14 @@ handle_call({create, Type, Name}, _From, State = #state{next_idx = NextIdx}) ->
true = ets:insert(?TAB, Metric), true = ets:insert(?TAB, Metric),
{reply, {ok, NextIdx}, State#state{next_idx = NextIdx + 1}} {reply, {ok, NextIdx}, State#state{next_idx = NextIdx + 1}}
end; end;
handle_call({set_type_to_counter, Keys}, _From, State) -> handle_call({set_type_to_counter, Keys}, _From, State) ->
lists:foreach( lists:foreach(
fun(K) -> fun(K) ->
ets:update_element(?TAB, K, {#metric.type, counter}) ets:update_element(?TAB, K, {#metric.type, counter})
end, Keys), end,
Keys
),
{reply, ok, State}; {reply, ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", req => Req}), ?SLOG(error, #{msg => "unexpected_call", req => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -506,100 +587,95 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
reserved_idx('bytes.received') -> 01; reserved_idx('bytes.received') -> 01;
reserved_idx('bytes.sent') -> 02; reserved_idx('bytes.sent') -> 02;
%% Reserved indices of packet's metrics %% Reserved indices of packet's metrics
reserved_idx('packets.received') -> 10; reserved_idx('packets.received') -> 10;
reserved_idx('packets.sent') -> 11; reserved_idx('packets.sent') -> 11;
reserved_idx('packets.connect.received') -> 12; reserved_idx('packets.connect.received') -> 12;
reserved_idx('packets.connack.sent') -> 13; reserved_idx('packets.connack.sent') -> 13;
reserved_idx('packets.connack.error') -> 14; reserved_idx('packets.connack.error') -> 14;
reserved_idx('packets.connack.auth_error') -> 15; reserved_idx('packets.connack.auth_error') -> 15;
reserved_idx('packets.publish.received') -> 16; reserved_idx('packets.publish.received') -> 16;
reserved_idx('packets.publish.sent') -> 17; reserved_idx('packets.publish.sent') -> 17;
reserved_idx('packets.publish.inuse') -> 18; reserved_idx('packets.publish.inuse') -> 18;
reserved_idx('packets.publish.error') -> 19; reserved_idx('packets.publish.error') -> 19;
reserved_idx('packets.publish.auth_error') -> 20; reserved_idx('packets.publish.auth_error') -> 20;
reserved_idx('packets.puback.received') -> 21; reserved_idx('packets.puback.received') -> 21;
reserved_idx('packets.puback.sent') -> 22; reserved_idx('packets.puback.sent') -> 22;
reserved_idx('packets.puback.inuse') -> 23; reserved_idx('packets.puback.inuse') -> 23;
reserved_idx('packets.puback.missed') -> 24; reserved_idx('packets.puback.missed') -> 24;
reserved_idx('packets.pubrec.received') -> 25; reserved_idx('packets.pubrec.received') -> 25;
reserved_idx('packets.pubrec.sent') -> 26; reserved_idx('packets.pubrec.sent') -> 26;
reserved_idx('packets.pubrec.inuse') -> 27; reserved_idx('packets.pubrec.inuse') -> 27;
reserved_idx('packets.pubrec.missed') -> 28; reserved_idx('packets.pubrec.missed') -> 28;
reserved_idx('packets.pubrel.received') -> 29; reserved_idx('packets.pubrel.received') -> 29;
reserved_idx('packets.pubrel.sent') -> 30; reserved_idx('packets.pubrel.sent') -> 30;
reserved_idx('packets.pubrel.missed') -> 31; reserved_idx('packets.pubrel.missed') -> 31;
reserved_idx('packets.pubcomp.received') -> 32; reserved_idx('packets.pubcomp.received') -> 32;
reserved_idx('packets.pubcomp.sent') -> 33; reserved_idx('packets.pubcomp.sent') -> 33;
reserved_idx('packets.pubcomp.inuse') -> 34; reserved_idx('packets.pubcomp.inuse') -> 34;
reserved_idx('packets.pubcomp.missed') -> 35; reserved_idx('packets.pubcomp.missed') -> 35;
reserved_idx('packets.subscribe.received') -> 36; reserved_idx('packets.subscribe.received') -> 36;
reserved_idx('packets.subscribe.error') -> 37; reserved_idx('packets.subscribe.error') -> 37;
reserved_idx('packets.subscribe.auth_error') -> 38; reserved_idx('packets.subscribe.auth_error') -> 38;
reserved_idx('packets.suback.sent') -> 39; reserved_idx('packets.suback.sent') -> 39;
reserved_idx('packets.unsubscribe.received') -> 40; reserved_idx('packets.unsubscribe.received') -> 40;
reserved_idx('packets.unsubscribe.error') -> 41; reserved_idx('packets.unsubscribe.error') -> 41;
reserved_idx('packets.unsuback.sent') -> 42; reserved_idx('packets.unsuback.sent') -> 42;
reserved_idx('packets.pingreq.received') -> 43; reserved_idx('packets.pingreq.received') -> 43;
reserved_idx('packets.pingresp.sent') -> 44; reserved_idx('packets.pingresp.sent') -> 44;
reserved_idx('packets.disconnect.received') -> 45; reserved_idx('packets.disconnect.received') -> 45;
reserved_idx('packets.disconnect.sent') -> 46; reserved_idx('packets.disconnect.sent') -> 46;
reserved_idx('packets.auth.received') -> 47; reserved_idx('packets.auth.received') -> 47;
reserved_idx('packets.auth.sent') -> 48; reserved_idx('packets.auth.sent') -> 48;
reserved_idx('packets.publish.dropped') -> 49; reserved_idx('packets.publish.dropped') -> 49;
%% Reserved indices of message's metrics %% Reserved indices of message's metrics
reserved_idx('messages.received') -> 100; reserved_idx('messages.received') -> 100;
reserved_idx('messages.sent') -> 101; reserved_idx('messages.sent') -> 101;
reserved_idx('messages.qos0.received') -> 102; reserved_idx('messages.qos0.received') -> 102;
reserved_idx('messages.qos0.sent') -> 103; reserved_idx('messages.qos0.sent') -> 103;
reserved_idx('messages.qos1.received') -> 104; reserved_idx('messages.qos1.received') -> 104;
reserved_idx('messages.qos1.sent') -> 105; reserved_idx('messages.qos1.sent') -> 105;
reserved_idx('messages.qos2.received') -> 106; reserved_idx('messages.qos2.received') -> 106;
reserved_idx('messages.qos2.sent') -> 107; reserved_idx('messages.qos2.sent') -> 107;
reserved_idx('messages.publish') -> 108; reserved_idx('messages.publish') -> 108;
reserved_idx('messages.dropped') -> 109; reserved_idx('messages.dropped') -> 109;
reserved_idx('messages.dropped.await_pubrel_timeout') -> 110; reserved_idx('messages.dropped.await_pubrel_timeout') -> 110;
reserved_idx('messages.dropped.no_subscribers') -> 111; reserved_idx('messages.dropped.no_subscribers') -> 111;
reserved_idx('messages.forward') -> 112; reserved_idx('messages.forward') -> 112;
%%reserved_idx('messages.retained') -> 113; %% keep the index, new metrics can use this %%reserved_idx('messages.retained') -> 113; %% keep the index, new metrics can use this
reserved_idx('messages.delayed') -> 114; reserved_idx('messages.delayed') -> 114;
reserved_idx('messages.delivered') -> 115; reserved_idx('messages.delivered') -> 115;
reserved_idx('messages.acked') -> 116; reserved_idx('messages.acked') -> 116;
reserved_idx('delivery.expired') -> 117; reserved_idx('delivery.expired') -> 117;
reserved_idx('delivery.dropped') -> 118; reserved_idx('delivery.dropped') -> 118;
reserved_idx('delivery.dropped.no_local') -> 119; reserved_idx('delivery.dropped.no_local') -> 119;
reserved_idx('delivery.dropped.too_large') -> 120; reserved_idx('delivery.dropped.too_large') -> 120;
reserved_idx('delivery.dropped.qos0_msg') -> 121; reserved_idx('delivery.dropped.qos0_msg') -> 121;
reserved_idx('delivery.dropped.queue_full') -> 122; reserved_idx('delivery.dropped.queue_full') -> 122;
reserved_idx('delivery.dropped.expired') -> 123; reserved_idx('delivery.dropped.expired') -> 123;
reserved_idx('client.connect') -> 200;
reserved_idx('client.connect') -> 200; reserved_idx('client.connack') -> 201;
reserved_idx('client.connack') -> 201; reserved_idx('client.connected') -> 202;
reserved_idx('client.connected') -> 202; reserved_idx('client.authenticate') -> 203;
reserved_idx('client.authenticate') -> 203;
reserved_idx('client.enhanced_authenticate') -> 204; reserved_idx('client.enhanced_authenticate') -> 204;
reserved_idx('client.auth.anonymous') -> 205; reserved_idx('client.auth.anonymous') -> 205;
reserved_idx('client.authorize') -> 206; reserved_idx('client.authorize') -> 206;
reserved_idx('client.subscribe') -> 207; reserved_idx('client.subscribe') -> 207;
reserved_idx('client.unsubscribe') -> 208; reserved_idx('client.unsubscribe') -> 208;
reserved_idx('client.disconnected') -> 209; reserved_idx('client.disconnected') -> 209;
reserved_idx('session.created') -> 220;
reserved_idx('session.created') -> 220; reserved_idx('session.resumed') -> 221;
reserved_idx('session.resumed') -> 221; reserved_idx('session.takenover') -> 222;
reserved_idx('session.takenover') -> 222; reserved_idx('session.discarded') -> 223;
reserved_idx('session.discarded') -> 223; reserved_idx('session.terminated') -> 224;
reserved_idx('session.terminated') -> 224; reserved_idx('client.acl.allow') -> 300;
reserved_idx('client.acl.deny') -> 301;
reserved_idx('client.acl.allow') -> 300; reserved_idx('client.acl.cache_hit') -> 302;
reserved_idx('client.acl.deny') -> 301; reserved_idx('olp.delay.ok') -> 400;
reserved_idx('client.acl.cache_hit') -> 302; reserved_idx('olp.delay.timeout') -> 401;
reserved_idx('olp.hbn') -> 402;
reserved_idx('olp.delay.ok') -> 400; reserved_idx('olp.gc') -> 403;
reserved_idx('olp.delay.timeout') -> 401; reserved_idx('olp.new_conn') -> 404;
reserved_idx('olp.hbn') -> 402; reserved_idx(_) -> undefined.
reserved_idx('olp.gc') -> 403;
reserved_idx('olp.new_conn') -> 404;
reserved_idx(_) -> undefined.

View File

@ -22,39 +22,41 @@
-include("types.hrl"). -include("types.hrl").
-include("logger.hrl"). -include("logger.hrl").
-export([ merge_opts/2 -export([
, maybe_apply/2 merge_opts/2,
, compose/1 maybe_apply/2,
, compose/2 compose/1,
, run_fold/3 compose/2,
, pipeline/3 run_fold/3,
, start_timer/2 pipeline/3,
, start_timer/3 start_timer/2,
, cancel_timer/1 start_timer/3,
, drain_deliver/0 cancel_timer/1,
, drain_deliver/1 drain_deliver/0,
, drain_down/1 drain_deliver/1,
, check_oom/1 drain_down/1,
, check_oom/2 check_oom/1,
, tune_heap_size/1 check_oom/2,
, proc_name/2 tune_heap_size/1,
, proc_stats/0 proc_name/2,
, proc_stats/1 proc_stats/0,
, rand_seed/0 proc_stats/1,
, now_to_secs/1 rand_seed/0,
, now_to_ms/1 now_to_secs/1,
, index_of/2 now_to_ms/1,
, maybe_parse_ip/1 index_of/2,
, ipv6_probe/1 maybe_parse_ip/1,
, gen_id/0 ipv6_probe/1,
, gen_id/1 gen_id/0,
, explain_posix/1 gen_id/1,
]). explain_posix/1
]).
-export([ bin2hexstr_A_F/1 -export([
, bin2hexstr_a_f/1 bin2hexstr_A_F/1,
, hexstr2bin/1 bin2hexstr_a_f/1,
]). hexstr2bin/1
]).
-export([clamp/3]). -export([clamp/3]).
@ -77,33 +79,43 @@ ipv6_probe(Opts) ->
end. end.
%% @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) ->
lists:foldl( lists:foldl(
fun({Opt, Val}, Acc) -> fun
lists:keystore(Opt, 1, Acc, {Opt, Val}); ({Opt, Val}, Acc) ->
(Opt, Acc) -> lists:keystore(Opt, 1, Acc, {Opt, Val});
lists:usort([Opt | Acc]) (Opt, Acc) ->
end, Defaults, Options). lists:usort([Opt | Acc])
end,
Defaults,
Options
).
%% @doc Apply a function to a maybe argument. %% @doc Apply a function to a maybe argument.
-spec(maybe_apply(fun((maybe(A)) -> maybe(A)), maybe(A)) -spec maybe_apply(fun((maybe(A)) -> maybe(A)), maybe(A)) ->
-> maybe(A) when A :: any()). maybe(A)
maybe_apply(_Fun, undefined) -> undefined; when
A :: any().
maybe_apply(_Fun, undefined) ->
undefined;
maybe_apply(Fun, Arg) when is_function(Fun) -> maybe_apply(Fun, Arg) when is_function(Fun) ->
erlang:apply(Fun, [Arg]). erlang:apply(Fun, [Arg]).
-spec(compose(list(F)) -> G -spec compose(list(F)) -> G when
when F :: fun((any()) -> any()), F :: fun((any()) -> any()),
G :: fun((any()) -> any())). G :: fun((any()) -> any()).
compose([F | More]) -> compose(F, More). compose([F | More]) -> compose(F, More).
-spec(compose(F, G | [Gs]) -> C -spec compose(F, G | [Gs]) -> C when
when F :: fun((X1) -> X2), F :: fun((X1) -> X2),
G :: fun((X2) -> X3), G :: fun((X2) -> X3),
Gs :: [fun((Xn) -> Xn1)], Gs :: [fun((Xn) -> Xn1)],
C :: fun((X1) -> Xm), C :: fun((X1) -> Xm),
X3 :: any(), Xn :: any(), Xn1 :: any(), Xm :: any()). X3 :: any(),
Xn :: any(),
Xn1 :: any(),
Xm :: any().
compose(F, G) when is_function(G) -> fun(X) -> G(F(X)) end; compose(F, G) when is_function(G) -> fun(X) -> G(F(X)) end;
compose(F, [G]) -> compose(F, G); compose(F, [G]) -> compose(F, G);
compose(F, [G | More]) -> compose(compose(F, G), More). compose(F, [G | More]) -> compose(compose(F, G), More).
@ -117,18 +129,13 @@ run_fold([Fun | More], Acc, State) ->
%% @doc Pipeline %% @doc Pipeline
pipeline([], Input, State) -> pipeline([], Input, State) ->
{ok, Input, State}; {ok, Input, State};
pipeline([Fun | More], Input, State) -> pipeline([Fun | More], Input, State) ->
case apply_fun(Fun, Input, State) of case apply_fun(Fun, Input, State) of
ok -> pipeline(More, Input, State); ok -> pipeline(More, Input, State);
{ok, NState} -> {ok, NState} -> pipeline(More, Input, NState);
pipeline(More, Input, NState); {ok, Output, NState} -> pipeline(More, Output, NState);
{ok, Output, NState} -> {error, Reason} -> {error, Reason, State};
pipeline(More, Output, NState); {error, Reason, NState} -> {error, Reason, NState}
{error, Reason} ->
{error, Reason, State};
{error, Reason, NState} ->
{error, Reason, NState}
end. end.
-compile({inline, [apply_fun/3]}). -compile({inline, [apply_fun/3]}).
@ -138,23 +145,29 @@ apply_fun(Fun, Input, State) ->
{arity, 2} -> Fun(Input, State) {arity, 2} -> Fun(Input, State)
end. end.
-spec(start_timer(integer() | atom(), term()) -> maybe(reference())). -spec start_timer(integer() | atom(), term()) -> maybe(reference()).
start_timer(Interval, Msg) -> start_timer(Interval, Msg) ->
start_timer(Interval, self(), Msg). start_timer(Interval, self(), Msg).
-spec(start_timer(integer() | atom(), pid() | atom(), term()) -> maybe(reference())). -spec start_timer(integer() | atom(), pid() | atom(), term()) -> maybe(reference()).
start_timer(Interval, Dest, Msg) when is_number(Interval) -> start_timer(Interval, Dest, Msg) when is_number(Interval) ->
erlang:start_timer(erlang:ceil(Interval), Dest, Msg); erlang:start_timer(erlang:ceil(Interval), Dest, Msg);
start_timer(_Atom, _Dest, _Msg) -> undefined. start_timer(_Atom, _Dest, _Msg) ->
undefined.
-spec(cancel_timer(maybe(reference())) -> ok). -spec cancel_timer(maybe(reference())) -> ok.
cancel_timer(Timer) when is_reference(Timer) -> cancel_timer(Timer) when is_reference(Timer) ->
case erlang:cancel_timer(Timer) of case erlang:cancel_timer(Timer) of
false -> false ->
receive {timeout, Timer, _} -> ok after 0 -> ok end; receive
_ -> ok {timeout, Timer, _} -> ok
after 0 -> ok
end;
_ ->
ok
end; end;
cancel_timer(_) -> ok. cancel_timer(_) ->
ok.
%% @doc Drain delivers %% @doc Drain delivers
drain_deliver() -> drain_deliver() ->
@ -168,13 +181,13 @@ drain_deliver(0, Acc) ->
drain_deliver(N, Acc) -> drain_deliver(N, Acc) ->
receive receive
Deliver = {deliver, _Topic, _Msg} -> Deliver = {deliver, _Topic, _Msg} ->
drain_deliver(N-1, [Deliver | Acc]) drain_deliver(N - 1, [Deliver | Acc])
after 0 -> after 0 ->
lists:reverse(Acc) lists:reverse(Acc)
end. end.
%% @doc Drain process 'DOWN' events. %% @doc Drain process 'DOWN' events.
-spec(drain_down(pos_integer()) -> list(pid())). -spec drain_down(pos_integer()) -> list(pid()).
drain_down(Cnt) when Cnt > 0 -> drain_down(Cnt) when Cnt > 0 ->
drain_down(Cnt, []). drain_down(Cnt, []).
@ -183,7 +196,7 @@ drain_down(0, Acc) ->
drain_down(Cnt, Acc) -> drain_down(Cnt, Acc) ->
receive receive
{'DOWN', _MRef, process, Pid, _Reason} -> {'DOWN', _MRef, process, Pid, _Reason} ->
drain_down(Cnt-1, [Pid | Acc]) drain_down(Cnt - 1, [Pid | Acc])
after 0 -> after 0 ->
lists:reverse(Acc) lists:reverse(Acc)
end. end.
@ -193,26 +206,32 @@ drain_down(Cnt, Acc) ->
%% `ok': There is nothing out of the ordinary. %% `ok': There is nothing out of the ordinary.
%% `shutdown': Some numbers (message queue length hit the limit), %% `shutdown': Some numbers (message queue length hit the limit),
%% hence shutdown for greater good (system stability). %% hence shutdown for greater good (system stability).
-spec(check_oom(emqx_types:oom_policy()) -> ok | {shutdown, term()}). -spec check_oom(emqx_types:oom_policy()) -> ok | {shutdown, term()}.
check_oom(Policy) -> check_oom(Policy) ->
check_oom(self(), Policy). check_oom(self(), Policy).
-spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}). -spec check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}.
check_oom(_Pid, #{enable := false}) -> ok; check_oom(_Pid, #{enable := false}) ->
check_oom(Pid, #{max_message_queue_len := MaxQLen, ok;
max_heap_size := MaxHeapSize}) -> check_oom(Pid, #{
max_message_queue_len := MaxQLen,
max_heap_size := MaxHeapSize
}) ->
case process_info(Pid, [message_queue_len, total_heap_size]) of case process_info(Pid, [message_queue_len, total_heap_size]) of
undefined -> ok; undefined ->
ok;
[{message_queue_len, QLen}, {total_heap_size, HeapSize}] -> [{message_queue_len, QLen}, {total_heap_size, HeapSize}] ->
do_check_oom([{QLen, MaxQLen, message_queue_too_long}, do_check_oom([
{HeapSize, MaxHeapSize, proc_heap_too_large} {QLen, MaxQLen, message_queue_too_long},
]) {HeapSize, MaxHeapSize, proc_heap_too_large}
])
end. end.
do_check_oom([]) -> ok; do_check_oom([]) ->
ok;
do_check_oom([{Val, Max, Reason} | Rest]) -> do_check_oom([{Val, Max, Reason} | Rest]) ->
case is_integer(Max) andalso (0 < Max) andalso (Max < Val) of case is_integer(Max) andalso (0 < Max) andalso (Max < Val) of
true -> {shutdown, Reason}; true -> {shutdown, Reason};
false -> do_check_oom(Rest) false -> do_check_oom(Rest)
end. end.
@ -220,53 +239,59 @@ tune_heap_size(#{enable := false}) ->
ok; ok;
%% If the max_heap_size is set to zero, the limit is disabled. %% If the max_heap_size is set to zero, the limit is disabled.
tune_heap_size(#{max_heap_size := MaxHeapSize}) when MaxHeapSize > 0 -> tune_heap_size(#{max_heap_size := MaxHeapSize}) when MaxHeapSize > 0 ->
MaxSize = case erlang:system_info(wordsize) of MaxSize =
8 -> % arch_64 case erlang:system_info(wordsize) of
(1 bsl 59) - 1; % arch_64
4 -> % arch_32 8 ->
(1 bsl 27) - 1 (1 bsl 59) - 1;
end, % arch_32
OverflowedSize = case erlang:trunc(MaxHeapSize * 1.5) of 4 ->
SZ when SZ > MaxSize -> MaxSize; (1 bsl 27) - 1
SZ -> SZ end,
end, OverflowedSize =
case erlang:trunc(MaxHeapSize * 1.5) of
SZ when SZ > MaxSize -> MaxSize;
SZ -> SZ
end,
erlang:process_flag(max_heap_size, #{ erlang:process_flag(max_heap_size, #{
size => OverflowedSize, size => OverflowedSize,
kill => true, kill => true,
error_logger => true error_logger => true
}). }).
-spec proc_name(atom(), pos_integer()) -> atom().
-spec(proc_name(atom(), pos_integer()) -> atom()).
proc_name(Mod, Id) -> proc_name(Mod, Id) ->
list_to_atom(lists:concat([Mod, "_", Id])). list_to_atom(lists:concat([Mod, "_", Id])).
%% Get Proc's Stats. %% Get Proc's Stats.
-spec(proc_stats() -> emqx_types:stats()). -spec proc_stats() -> emqx_types:stats().
proc_stats() -> proc_stats(self()). proc_stats() -> proc_stats(self()).
-spec(proc_stats(pid()) -> emqx_types:stats()). -spec proc_stats(pid()) -> emqx_types:stats().
proc_stats(Pid) -> proc_stats(Pid) ->
case process_info(Pid, [message_queue_len, case
heap_size, process_info(Pid, [
total_heap_size, message_queue_len,
reductions, heap_size,
memory]) of total_heap_size,
reductions,
memory
])
of
undefined -> []; undefined -> [];
[{message_queue_len, Len} | ProcStats] -> [{message_queue_len, Len} | ProcStats] -> [{mailbox_len, Len} | ProcStats]
[{mailbox_len, Len} | ProcStats]
end. end.
rand_seed() -> rand_seed() ->
rand:seed(exsplus, erlang:timestamp()). rand:seed(exsplus, erlang:timestamp()).
-spec(now_to_secs(erlang:timestamp()) -> pos_integer()). -spec now_to_secs(erlang:timestamp()) -> pos_integer().
now_to_secs({MegaSecs, Secs, _MicroSecs}) -> now_to_secs({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs. MegaSecs * 1000000 + Secs.
-spec(now_to_ms(erlang:timestamp()) -> pos_integer()). -spec now_to_ms(erlang:timestamp()) -> pos_integer().
now_to_ms({MegaSecs, Secs, MicroSecs}) -> now_to_ms({MegaSecs, Secs, MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs / 1000).
%% lists:index_of/2 %% lists:index_of/2
index_of(E, L) -> index_of(E, L) ->
@ -277,33 +302,33 @@ index_of(_E, _I, []) ->
index_of(E, I, [E | _]) -> index_of(E, I, [E | _]) ->
I; I;
index_of(E, I, [_ | L]) -> index_of(E, I, [_ | L]) ->
index_of(E, I+1, L). index_of(E, I + 1, L).
-spec(bin2hexstr_A_F(binary()) -> binary()). -spec bin2hexstr_A_F(binary()) -> binary().
bin2hexstr_A_F(B) when is_binary(B) -> bin2hexstr_A_F(B) when is_binary(B) ->
<< <<(int2hexchar(H, upper)), (int2hexchar(L, upper))>> || <<H:4, L:4>> <= B>>. <<<<(int2hexchar(H, upper)), (int2hexchar(L, upper))>> || <<H:4, L:4>> <= B>>.
-spec(bin2hexstr_a_f(binary()) -> binary()). -spec bin2hexstr_a_f(binary()) -> binary().
bin2hexstr_a_f(B) when is_binary(B) -> bin2hexstr_a_f(B) when is_binary(B) ->
<< <<(int2hexchar(H, lower)), (int2hexchar(L, lower))>> || <<H:4, L:4>> <= B>>. <<<<(int2hexchar(H, lower)), (int2hexchar(L, lower))>> || <<H:4, L:4>> <= B>>.
int2hexchar(I, _) when I >= 0 andalso I < 10 -> I + $0; int2hexchar(I, _) when I >= 0 andalso I < 10 -> I + $0;
int2hexchar(I, upper) -> I - 10 + $A; int2hexchar(I, upper) -> I - 10 + $A;
int2hexchar(I, lower) -> I - 10 + $a. int2hexchar(I, lower) -> I - 10 + $a.
-spec(hexstr2bin(binary()) -> binary()). -spec hexstr2bin(binary()) -> binary().
hexstr2bin(B) when is_binary(B) -> hexstr2bin(B) when is_binary(B) ->
<< <<(hexchar2int(H)*16 + hexchar2int(L))>> || <<H:8, L:8>> <= B>>. <<<<(hexchar2int(H) * 16 + hexchar2int(L))>> || <<H:8, L:8>> <= B>>.
hexchar2int(I) when I >= $0 andalso I =< $9 -> I - $0; hexchar2int(I) when I >= $0 andalso I =< $9 -> I - $0;
hexchar2int(I) when I >= $A andalso I =< $F -> I - $A + 10; hexchar2int(I) when I >= $A andalso I =< $F -> I - $A + 10;
hexchar2int(I) when I >= $a andalso I =< $f -> I - $a + 10. hexchar2int(I) when I >= $a andalso I =< $f -> I - $a + 10.
-spec(gen_id() -> list()). -spec gen_id() -> list().
gen_id() -> gen_id() ->
gen_id(?SHORT). gen_id(?SHORT).
-spec(gen_id(integer()) -> list()). -spec gen_id(integer()) -> list().
gen_id(Len) -> gen_id(Len) ->
BitLen = Len * 4, BitLen = Len * 4,
<<R:BitLen>> = crypto:strong_rand_bytes(Len div 2), <<R:BitLen>> = crypto:strong_rand_bytes(Len div 2),
@ -356,8 +381,9 @@ explain_posix(NotPosix) -> NotPosix.
int_to_hex(I, N) when is_integer(I), I >= 0 -> int_to_hex(I, N) when is_integer(I), I >= 0 ->
int_to_hex([], I, 1, N). int_to_hex([], I, 1, N).
int_to_hex(L, I, Count, N) int_to_hex(L, I, Count, N) when
when I < 16 -> I < 16
->
pad([int_to_hex(I) | L], N - Count); pad([int_to_hex(I) | L], N - Count);
int_to_hex(L, I, Count, N) -> int_to_hex(L, I, Count, N) ->
int_to_hex([int_to_hex(I rem 16) | L], I div 16, Count + 1, N). int_to_hex([int_to_hex(I rem 16) | L], I div 16, Count + 1, N).
@ -380,7 +406,7 @@ ipv6_probe_test() ->
true -> true ->
?assertEqual([{ipv6_probe, true}], ipv6_probe([])) ?assertEqual([{ipv6_probe, true}], ipv6_probe([]))
catch catch
_ : _ -> _:_ ->
ok ok
end. end.

View File

@ -20,20 +20,22 @@
-include("emqx_placeholder.hrl"). -include("emqx_placeholder.hrl").
-include("types.hrl"). -include("types.hrl").
-export([ mount/2 -export([
, unmount/2 mount/2,
]). unmount/2
]).
-export([replvar/2]). -export([replvar/2]).
-export_type([mountpoint/0]). -export_type([mountpoint/0]).
-type(mountpoint() :: binary()). -type mountpoint() :: binary().
-spec(mount(maybe(mountpoint()), Any) -> Any -spec mount(maybe(mountpoint()), Any) -> Any when
when Any :: emqx_types:topic() Any ::
| emqx_types:message() emqx_types:topic()
| emqx_types:topic_filters()). | emqx_types:message()
| emqx_types:topic_filters().
mount(undefined, Any) -> mount(undefined, Any) ->
Any; Any;
mount(MountPoint, Topic) when is_binary(Topic) -> mount(MountPoint, Topic) when is_binary(Topic) ->
@ -48,33 +50,35 @@ mount(MountPoint, TopicFilters) when is_list(TopicFilters) ->
prefix(MountPoint, Topic) -> prefix(MountPoint, Topic) ->
<<MountPoint/binary, Topic/binary>>. <<MountPoint/binary, Topic/binary>>.
-spec(unmount(maybe(mountpoint()), Any) -> Any -spec unmount(maybe(mountpoint()), Any) -> Any when
when Any :: emqx_types:topic() Any ::
| emqx_types:message()). emqx_types:topic()
| emqx_types:message().
unmount(undefined, Any) -> unmount(undefined, Any) ->
Any; Any;
unmount(MountPoint, Topic) when is_binary(Topic) -> unmount(MountPoint, Topic) when is_binary(Topic) ->
case string:prefix(Topic, MountPoint) of case string:prefix(Topic, MountPoint) of
nomatch -> Topic; nomatch -> Topic;
Topic1 -> Topic1 Topic1 -> Topic1
end; end;
unmount(MountPoint, Msg = #message{topic = Topic}) -> unmount(MountPoint, Msg = #message{topic = Topic}) ->
case string:prefix(Topic, MountPoint) of case string:prefix(Topic, MountPoint) of
nomatch -> Msg; nomatch -> Msg;
Topic1 -> Msg#message{topic = Topic1} Topic1 -> Msg#message{topic = Topic1}
end. end.
-spec(replvar(maybe(mountpoint()), map()) -> maybe(mountpoint())). -spec replvar(maybe(mountpoint()), map()) -> maybe(mountpoint()).
replvar(undefined, _Vars) -> replvar(undefined, _Vars) ->
undefined; undefined;
replvar(MountPoint, Vars) -> replvar(MountPoint, Vars) ->
ClientID = maps:get(clientid, Vars, undefined), ClientID = maps:get(clientid, Vars, undefined),
UserName = maps:get(username, Vars, undefined), UserName = maps:get(username, Vars, undefined),
EndpointName = maps:get(endpoint_name, Vars, undefined), EndpointName = maps:get(endpoint_name, Vars, undefined),
List = [ {?PH_CLIENTID, ClientID} List = [
, {?PH_USERNAME, UserName} {?PH_CLIENTID, ClientID},
, {?PH_ENDPOINT_NAME, EndpointName} {?PH_USERNAME, UserName},
], {?PH_ENDPOINT_NAME, EndpointName}
],
lists:foldl(fun feed_var/2, MountPoint, List). lists:foldl(fun feed_var/2, MountPoint, List).
feed_var({_PlaceHolder, undefined}, MountPoint) -> feed_var({_PlaceHolder, undefined}, MountPoint) ->

View File

@ -20,100 +20,128 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-include("types.hrl"). -include("types.hrl").
-export([ check_pub/2 -export([
, check_sub/3 check_pub/2,
]). check_sub/3
]).
-export([ get_caps/1 -export([get_caps/1]).
]).
-export_type([caps/0]). -export_type([caps/0]).
-type(caps() :: #{max_packet_size => integer(), -type caps() :: #{
max_clientid_len => integer(), max_packet_size => integer(),
max_topic_alias => integer(), max_clientid_len => integer(),
max_topic_levels => integer(), max_topic_alias => integer(),
max_qos_allowed => emqx_types:qos(), max_topic_levels => integer(),
retain_available => boolean(), max_qos_allowed => emqx_types:qos(),
wildcard_subscription => boolean(), retain_available => boolean(),
subscription_identifiers => boolean(), wildcard_subscription => boolean(),
shared_subscription => boolean() subscription_identifiers => boolean(),
}). shared_subscription => boolean()
}.
-define(MAX_TOPIC_LEVELS, 65535). -define(MAX_TOPIC_LEVELS, 65535).
-define(PUBCAP_KEYS, [max_topic_levels, -define(PUBCAP_KEYS, [
max_qos_allowed, max_topic_levels,
retain_available max_qos_allowed,
]). retain_available
]).
-define(SUBCAP_KEYS, [max_topic_levels, -define(SUBCAP_KEYS, [
max_qos_allowed, max_topic_levels,
wildcard_subscription, max_qos_allowed,
shared_subscription wildcard_subscription,
]). shared_subscription
]).
-define(DEFAULT_CAPS, #{max_packet_size => ?MAX_PACKET_SIZE, -define(DEFAULT_CAPS, #{
max_clientid_len => ?MAX_CLIENTID_LEN, max_packet_size => ?MAX_PACKET_SIZE,
max_topic_alias => ?MAX_TOPIC_AlIAS, max_clientid_len => ?MAX_CLIENTID_LEN,
max_topic_levels => ?MAX_TOPIC_LEVELS, max_topic_alias => ?MAX_TOPIC_AlIAS,
max_qos_allowed => ?QOS_2, max_topic_levels => ?MAX_TOPIC_LEVELS,
retain_available => true, max_qos_allowed => ?QOS_2,
wildcard_subscription => true, retain_available => true,
subscription_identifiers => true, wildcard_subscription => true,
shared_subscription => true subscription_identifiers => true,
}). shared_subscription => true
}).
-spec(check_pub(emqx_types:zone(), -spec check_pub(
#{qos := emqx_types:qos(), emqx_types:zone(),
retain := boolean(), #{
topic := emqx_types:topic()}) qos := emqx_types:qos(),
-> ok_or_error(emqx_types:reason_code())). retain := boolean(),
topic := emqx_types:topic()
}
) ->
ok_or_error(emqx_types:reason_code()).
check_pub(Zone, Flags) when is_map(Flags) -> check_pub(Zone, Flags) when is_map(Flags) ->
do_check_pub(case maps:take(topic, Flags) of do_check_pub(
{Topic, Flags1} -> case maps:take(topic, Flags) of
Flags1#{topic_levels => emqx_topic:levels(Topic)}; {Topic, Flags1} ->
error -> Flags1#{topic_levels => emqx_topic:levels(Topic)};
Flags error ->
end, maps:with(?PUBCAP_KEYS, get_caps(Zone))). Flags
end,
maps:with(?PUBCAP_KEYS, get_caps(Zone))
).
do_check_pub(#{topic_levels := Levels}, #{max_topic_levels := Limit}) do_check_pub(#{topic_levels := Levels}, #{max_topic_levels := Limit}) when
when Limit > 0, Levels > Limit -> Limit > 0, Levels > Limit
->
{error, ?RC_TOPIC_NAME_INVALID}; {error, ?RC_TOPIC_NAME_INVALID};
do_check_pub(#{qos := QoS}, #{max_qos_allowed := MaxQoS}) do_check_pub(#{qos := QoS}, #{max_qos_allowed := MaxQoS}) when
when QoS > MaxQoS -> QoS > MaxQoS
->
{error, ?RC_QOS_NOT_SUPPORTED}; {error, ?RC_QOS_NOT_SUPPORTED};
do_check_pub(#{retain := true}, #{retain_available := false}) -> do_check_pub(#{retain := true}, #{retain_available := false}) ->
{error, ?RC_RETAIN_NOT_SUPPORTED}; {error, ?RC_RETAIN_NOT_SUPPORTED};
do_check_pub(_Flags, _Caps) -> ok. do_check_pub(_Flags, _Caps) ->
ok.
-spec(check_sub(emqx_types:zone(), -spec check_sub(
emqx_types:topic(), emqx_types:zone(),
emqx_types:subopts()) emqx_types:topic(),
-> ok_or_error(emqx_types:reason_code())). emqx_types:subopts()
) ->
ok_or_error(emqx_types:reason_code()).
check_sub(Zone, Topic, SubOpts) -> check_sub(Zone, Topic, SubOpts) ->
Caps = maps:with(?SUBCAP_KEYS, get_caps(Zone)), Caps = maps:with(?SUBCAP_KEYS, get_caps(Zone)),
Flags = lists:foldl( Flags = lists:foldl(
fun(max_topic_levels, Map) -> fun
Map#{topic_levels => emqx_topic:levels(Topic)}; (max_topic_levels, Map) ->
(wildcard_subscription, Map) -> Map#{topic_levels => emqx_topic:levels(Topic)};
Map#{is_wildcard => emqx_topic:wildcard(Topic)}; (wildcard_subscription, Map) ->
(shared_subscription, Map) -> Map#{is_wildcard => emqx_topic:wildcard(Topic)};
Map#{is_shared => maps:is_key(share, SubOpts)}; (shared_subscription, Map) ->
(_Key, Map) -> Map %% Ignore Map#{is_shared => maps:is_key(share, SubOpts)};
end, #{}, maps:keys(Caps)), %% Ignore
(_Key, Map) ->
Map
end,
#{},
maps:keys(Caps)
),
do_check_sub(Flags, Caps). do_check_sub(Flags, Caps).
do_check_sub(#{topic_levels := Levels}, #{max_topic_levels := Limit}) do_check_sub(#{topic_levels := Levels}, #{max_topic_levels := Limit}) when
when Limit > 0, Levels > Limit -> Limit > 0, Levels > Limit
->
{error, ?RC_TOPIC_FILTER_INVALID}; {error, ?RC_TOPIC_FILTER_INVALID};
do_check_sub(#{is_wildcard := true}, #{wildcard_subscription := false}) -> do_check_sub(#{is_wildcard := true}, #{wildcard_subscription := false}) ->
{error, ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED}; {error, ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED};
do_check_sub(#{is_shared := true}, #{shared_subscription := false}) -> do_check_sub(#{is_shared := true}, #{shared_subscription := false}) ->
{error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}; {error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED};
do_check_sub(_Flags, _Caps) -> ok. do_check_sub(_Flags, _Caps) ->
ok.
get_caps(Zone) -> get_caps(Zone) ->
lists:foldl(fun({K, V}, Acc) -> lists:foldl(
fun({K, V}, Acc) ->
Acc#{K => emqx_config:get_zone_conf(Zone, [mqtt, K], V)} Acc#{K => emqx_config:get_zone_conf(Zone, [mqtt, K], V)}
end, #{}, maps:to_list(?DEFAULT_CAPS)). end,
#{},
maps:to_list(?DEFAULT_CAPS)
).

View File

@ -19,86 +19,97 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-export([ id/1 -export([
, name/1 id/1,
, filter/2 name/1,
, validate/1 filter/2,
, new/0 validate/1,
]). new/0
]).
%% For tests %% For tests
-export([all/0]). -export([all/0]).
-export([ set/3 -export([
, get/3 set/3,
]). get/3
]).
-type(prop_name() :: atom()). -type prop_name() :: atom().
-type(prop_id() :: pos_integer()). -type prop_id() :: pos_integer().
-define(PROPS_TABLE, -define(PROPS_TABLE, #{
#{16#01 => {'Payload-Format-Indicator', 'Byte', [?PUBLISH]}, 16#01 => {'Payload-Format-Indicator', 'Byte', [?PUBLISH]},
16#02 => {'Message-Expiry-Interval', 'Four-Byte-Integer', [?PUBLISH]}, 16#02 => {'Message-Expiry-Interval', 'Four-Byte-Integer', [?PUBLISH]},
16#03 => {'Content-Type', 'UTF8-Encoded-String', [?PUBLISH]}, 16#03 => {'Content-Type', 'UTF8-Encoded-String', [?PUBLISH]},
16#08 => {'Response-Topic', 'UTF8-Encoded-String', [?PUBLISH]}, 16#08 => {'Response-Topic', 'UTF8-Encoded-String', [?PUBLISH]},
16#09 => {'Correlation-Data', 'Binary-Data', [?PUBLISH]}, 16#09 => {'Correlation-Data', 'Binary-Data', [?PUBLISH]},
16#0B => {'Subscription-Identifier', 'Variable-Byte-Integer', [?PUBLISH, ?SUBSCRIBE]}, 16#0B => {'Subscription-Identifier', 'Variable-Byte-Integer', [?PUBLISH, ?SUBSCRIBE]},
16#11 => {'Session-Expiry-Interval', 'Four-Byte-Integer', [?CONNECT, ?CONNACK, ?DISCONNECT]}, 16#11 => {'Session-Expiry-Interval', 'Four-Byte-Integer', [?CONNECT, ?CONNACK, ?DISCONNECT]},
16#12 => {'Assigned-Client-Identifier', 'UTF8-Encoded-String', [?CONNACK]}, 16#12 => {'Assigned-Client-Identifier', 'UTF8-Encoded-String', [?CONNACK]},
16#13 => {'Server-Keep-Alive', 'Two-Byte-Integer', [?CONNACK]}, 16#13 => {'Server-Keep-Alive', 'Two-Byte-Integer', [?CONNACK]},
16#15 => {'Authentication-Method', 'UTF8-Encoded-String', [?CONNECT, ?CONNACK, ?AUTH]}, 16#15 => {'Authentication-Method', 'UTF8-Encoded-String', [?CONNECT, ?CONNACK, ?AUTH]},
16#16 => {'Authentication-Data', 'Binary-Data', [?CONNECT, ?CONNACK, ?AUTH]}, 16#16 => {'Authentication-Data', 'Binary-Data', [?CONNECT, ?CONNACK, ?AUTH]},
16#17 => {'Request-Problem-Information', 'Byte', [?CONNECT]}, 16#17 => {'Request-Problem-Information', 'Byte', [?CONNECT]},
16#18 => {'Will-Delay-Interval', 'Four-Byte-Integer', ['WILL']}, 16#18 => {'Will-Delay-Interval', 'Four-Byte-Integer', ['WILL']},
16#19 => {'Request-Response-Information', 'Byte', [?CONNECT]}, 16#19 => {'Request-Response-Information', 'Byte', [?CONNECT]},
16#1A => {'Response-Information', 'UTF8-Encoded-String', [?CONNACK]}, 16#1A => {'Response-Information', 'UTF8-Encoded-String', [?CONNACK]},
16#1C => {'Server-Reference', 'UTF8-Encoded-String', [?CONNACK, ?DISCONNECT]}, 16#1C => {'Server-Reference', 'UTF8-Encoded-String', [?CONNACK, ?DISCONNECT]},
16#1F => {'Reason-String', 'UTF8-Encoded-String', [?CONNACK, ?DISCONNECT, ?PUBACK, 16#1F =>
?PUBREC, ?PUBREL, ?PUBCOMP, {'Reason-String', 'UTF8-Encoded-String', [
?SUBACK, ?UNSUBACK, ?AUTH]}, ?CONNACK,
16#21 => {'Receive-Maximum', 'Two-Byte-Integer', [?CONNECT, ?CONNACK]}, ?DISCONNECT,
16#22 => {'Topic-Alias-Maximum', 'Two-Byte-Integer', [?CONNECT, ?CONNACK]}, ?PUBACK,
16#23 => {'Topic-Alias', 'Two-Byte-Integer', [?PUBLISH]}, ?PUBREC,
16#24 => {'Maximum-QoS', 'Byte', [?CONNACK]}, ?PUBREL,
16#25 => {'Retain-Available', 'Byte', [?CONNACK]}, ?PUBCOMP,
16#26 => {'User-Property', 'UTF8-String-Pair', 'ALL'}, ?SUBACK,
16#27 => {'Maximum-Packet-Size', 'Four-Byte-Integer', [?CONNECT, ?CONNACK]}, ?UNSUBACK,
16#28 => {'Wildcard-Subscription-Available', 'Byte', [?CONNACK]}, ?AUTH
16#29 => {'Subscription-Identifier-Available', 'Byte', [?CONNACK]}, ]},
16#2A => {'Shared-Subscription-Available', 'Byte', [?CONNACK]} 16#21 => {'Receive-Maximum', 'Two-Byte-Integer', [?CONNECT, ?CONNACK]},
}). 16#22 => {'Topic-Alias-Maximum', 'Two-Byte-Integer', [?CONNECT, ?CONNACK]},
16#23 => {'Topic-Alias', 'Two-Byte-Integer', [?PUBLISH]},
16#24 => {'Maximum-QoS', 'Byte', [?CONNACK]},
16#25 => {'Retain-Available', 'Byte', [?CONNACK]},
16#26 => {'User-Property', 'UTF8-String-Pair', 'ALL'},
16#27 => {'Maximum-Packet-Size', 'Four-Byte-Integer', [?CONNECT, ?CONNACK]},
16#28 => {'Wildcard-Subscription-Available', 'Byte', [?CONNACK]},
16#29 => {'Subscription-Identifier-Available', 'Byte', [?CONNACK]},
16#2A => {'Shared-Subscription-Available', 'Byte', [?CONNACK]}
}).
-spec(id(prop_name()) -> prop_id()). -spec id(prop_name()) -> prop_id().
id('Payload-Format-Indicator') -> 16#01; id('Payload-Format-Indicator') -> 16#01;
id('Message-Expiry-Interval') -> 16#02; id('Message-Expiry-Interval') -> 16#02;
id('Content-Type') -> 16#03; id('Content-Type') -> 16#03;
id('Response-Topic') -> 16#08; id('Response-Topic') -> 16#08;
id('Correlation-Data') -> 16#09; id('Correlation-Data') -> 16#09;
id('Subscription-Identifier') -> 16#0B; id('Subscription-Identifier') -> 16#0B;
id('Session-Expiry-Interval') -> 16#11; id('Session-Expiry-Interval') -> 16#11;
id('Assigned-Client-Identifier') -> 16#12; id('Assigned-Client-Identifier') -> 16#12;
id('Server-Keep-Alive') -> 16#13; id('Server-Keep-Alive') -> 16#13;
id('Authentication-Method') -> 16#15; id('Authentication-Method') -> 16#15;
id('Authentication-Data') -> 16#16; id('Authentication-Data') -> 16#16;
id('Request-Problem-Information') -> 16#17; id('Request-Problem-Information') -> 16#17;
id('Will-Delay-Interval') -> 16#18; id('Will-Delay-Interval') -> 16#18;
id('Request-Response-Information') -> 16#19; id('Request-Response-Information') -> 16#19;
id('Response-Information') -> 16#1A; id('Response-Information') -> 16#1A;
id('Server-Reference') -> 16#1C; id('Server-Reference') -> 16#1C;
id('Reason-String') -> 16#1F; id('Reason-String') -> 16#1F;
id('Receive-Maximum') -> 16#21; id('Receive-Maximum') -> 16#21;
id('Topic-Alias-Maximum') -> 16#22; id('Topic-Alias-Maximum') -> 16#22;
id('Topic-Alias') -> 16#23; id('Topic-Alias') -> 16#23;
id('Maximum-QoS') -> 16#24; id('Maximum-QoS') -> 16#24;
id('Retain-Available') -> 16#25; id('Retain-Available') -> 16#25;
id('User-Property') -> 16#26; id('User-Property') -> 16#26;
id('Maximum-Packet-Size') -> 16#27; id('Maximum-Packet-Size') -> 16#27;
id('Wildcard-Subscription-Available') -> 16#28; id('Wildcard-Subscription-Available') -> 16#28;
id('Subscription-Identifier-Available') -> 16#29; id('Subscription-Identifier-Available') -> 16#29;
id('Shared-Subscription-Available') -> 16#2A; id('Shared-Subscription-Available') -> 16#2A;
id(Name) -> error({bad_property, Name}). id(Name) -> error({bad_property, Name}).
-spec(name(prop_id()) -> prop_name()). -spec name(prop_id()) -> prop_name().
name(16#01) -> 'Payload-Format-Indicator'; name(16#01) -> 'Payload-Format-Indicator';
name(16#02) -> 'Message-Expiry-Interval'; name(16#02) -> 'Message-Expiry-Interval';
name(16#03) -> 'Content-Type'; name(16#03) -> 'Content-Type';
@ -126,33 +137,36 @@ name(16#27) -> 'Maximum-Packet-Size';
name(16#28) -> 'Wildcard-Subscription-Available'; name(16#28) -> 'Wildcard-Subscription-Available';
name(16#29) -> 'Subscription-Identifier-Available'; name(16#29) -> 'Subscription-Identifier-Available';
name(16#2A) -> 'Shared-Subscription-Available'; name(16#2A) -> 'Shared-Subscription-Available';
name(Id) -> error({unsupported_property, Id}). name(Id) -> error({unsupported_property, Id}).
-spec(filter(emqx_types:packet_type(), emqx_types:properties()) -spec filter(emqx_types:packet_type(), emqx_types:properties()) ->
-> emqx_types:properties()). emqx_types:properties().
filter(PacketType, Props) when is_map(Props), filter(PacketType, Props) when
PacketType >= ?CONNECT, is_map(Props),
PacketType =< ?AUTH -> PacketType >= ?CONNECT,
PacketType =< ?AUTH
->
F = fun(Name, _) -> F = fun(Name, _) ->
case maps:find(id(Name), ?PROPS_TABLE) of case maps:find(id(Name), ?PROPS_TABLE) of
{ok, {Name, _Type, 'ALL'}} -> {ok, {Name, _Type, 'ALL'}} ->
true; true;
{ok, {Name, _Type, AllowedTypes}} -> {ok, {Name, _Type, AllowedTypes}} ->
lists:member(PacketType, AllowedTypes); lists:member(PacketType, AllowedTypes);
error -> false error ->
end false
end, end
end,
maps:filter(F, Props). maps:filter(F, Props).
-spec(validate(emqx_types:properties()) -> ok). -spec validate(emqx_types:properties()) -> ok.
validate(Props) when is_map(Props) -> validate(Props) when is_map(Props) ->
lists:foreach(fun validate_prop/1, maps:to_list(Props)). lists:foreach(fun validate_prop/1, maps:to_list(Props)).
validate_prop(Prop = {Name, Val}) -> validate_prop(Prop = {Name, Val}) ->
case maps:find(id(Name), ?PROPS_TABLE) of case maps:find(id(Name), ?PROPS_TABLE) of
{ok, {Name, Type, _}} -> {ok, {Name, Type, _}} ->
validate_value(Type, Val) validate_value(Type, Val) orelse
orelse error({bad_property_value, Prop}); error({bad_property_value, Prop});
error -> error ->
error({bad_property, Name}) error({bad_property, Name})
end. end.
@ -166,23 +180,28 @@ validate_value('Four-Byte-Integer', Val) ->
validate_value('Variable-Byte-Integer', Val) -> validate_value('Variable-Byte-Integer', Val) ->
is_integer(Val) andalso 0 =< Val andalso Val =< 16#7FFFFFFF; is_integer(Val) andalso 0 =< Val andalso Val =< 16#7FFFFFFF;
validate_value('UTF8-String-Pair', {Name, Val}) -> validate_value('UTF8-String-Pair', {Name, Val}) ->
validate_value('UTF8-Encoded-String', Name) validate_value('UTF8-Encoded-String', Name) andalso
andalso validate_value('UTF8-Encoded-String', Val); validate_value('UTF8-Encoded-String', Val);
validate_value('UTF8-String-Pair', Pairs) when is_list(Pairs) -> validate_value('UTF8-String-Pair', Pairs) when is_list(Pairs) ->
lists:foldl(fun(Pair, OK) -> lists:foldl(
OK andalso validate_value('UTF8-String-Pair', Pair) fun(Pair, OK) ->
end, true, Pairs); OK andalso validate_value('UTF8-String-Pair', Pair)
validate_value('UTF8-Encoded-String', Val) -> end,
true,
Pairs
);
validate_value('UTF8-Encoded-String', Val) ->
is_binary(Val); is_binary(Val);
validate_value('Binary-Data', Val) -> validate_value('Binary-Data', Val) ->
is_binary(Val); is_binary(Val);
validate_value(_Type, _Val) -> false. validate_value(_Type, _Val) ->
false.
-spec(new() -> map()). -spec new() -> map().
new() -> new() ->
#{}. #{}.
-spec(all() -> map()). -spec all() -> map().
all() -> ?PROPS_TABLE. all() -> ?PROPS_TABLE.
set(Name, Value, undefined) -> set(Name, Value, undefined) ->
@ -194,4 +213,3 @@ get(_Name, undefined, Default) ->
Default; Default;
get(Name, Props, Default) -> get(Name, Props, Default) ->
maps:get(Name, Props, Default). maps:get(Name, Props, Default).

View File

@ -53,39 +53,43 @@
-include("types.hrl"). -include("types.hrl").
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-export([ init/1 -export([
, info/1 init/1,
, info/2 info/1,
]). info/2
]).
-export([ is_empty/1 -export([
, len/1 is_empty/1,
, max_len/1 len/1,
, in/2 max_len/1,
, out/1 in/2,
, stats/1 out/1,
, dropped/1 stats/1,
]). dropped/1
]).
-define(NO_PRIORITY_TABLE, disabled). -define(NO_PRIORITY_TABLE, disabled).
-export_type([mqueue/0, options/0]). -export_type([mqueue/0, options/0]).
-type(topic() :: emqx_types:topic()). -type topic() :: emqx_types:topic().
-type(priority() :: infinity | integer()). -type priority() :: infinity | integer().
-type(pq() :: emqx_pqueue:q()). -type pq() :: emqx_pqueue:q().
-type(count() :: non_neg_integer()). -type count() :: non_neg_integer().
-type(p_table() :: ?NO_PRIORITY_TABLE | #{topic() := priority()}). -type p_table() :: ?NO_PRIORITY_TABLE | #{topic() := priority()}.
-type(options() :: #{max_len := count(), -type options() :: #{
priorities => p_table(), max_len := count(),
default_priority => highest | lowest, priorities => p_table(),
store_qos0 => boolean() default_priority => highest | lowest,
}). store_qos0 => boolean()
-type(message() :: emqx_types:message()). }.
-type message() :: emqx_types:message().
-type(stat() :: {len, non_neg_integer()} -type stat() ::
| {max_len, non_neg_integer()} {len, non_neg_integer()}
| {dropped, non_neg_integer()}). | {max_len, non_neg_integer()}
| {dropped, non_neg_integer()}.
-define(PQUEUE, emqx_pqueue). -define(PQUEUE, emqx_pqueue).
-define(LOWEST_PRIORITY, 0). -define(LOWEST_PRIORITY, 0).
@ -94,43 +98,45 @@
-define(INFO_KEYS, [store_qos0, max_len, len, dropped]). -define(INFO_KEYS, [store_qos0, max_len, len, dropped]).
-record(shift_opts, { -record(shift_opts, {
multiplier :: non_neg_integer(), multiplier :: non_neg_integer(),
base :: 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(),
len = 0 :: count(), len = 0 :: count(),
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{}, shift_opts :: #shift_opts{},
last_prio :: non_neg_integer() | undefined, last_prio :: non_neg_integer() | undefined,
p_credit :: non_neg_integer() | undefined p_credit :: non_neg_integer() | undefined
}). }).
-type(mqueue() :: #mqueue{}). -type mqueue() :: #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 =
true -> MaxLen0; case (is_integer(MaxLen0) andalso MaxLen0 > ?MAX_LEN_INFINITY) of
false -> ?MAX_LEN_INFINITY true -> MaxLen0;
end, false -> ?MAX_LEN_INFINITY
#mqueue{max_len = MaxLen, end,
store_qos0 = QoS_0, #mqueue{
p_table = get_opt(priorities, Opts, ?NO_PRIORITY_TABLE), max_len = MaxLen,
default_p = get_priority_opt(Opts), store_qos0 = QoS_0,
shift_opts = get_shift_opt(Opts) p_table = get_opt(priorities, Opts, ?NO_PRIORITY_TABLE),
}. default_p = get_priority_opt(Opts),
shift_opts = get_shift_opt(Opts)
}.
-spec(info(mqueue()) -> emqx_types:infos()). -spec info(mqueue()) -> emqx_types:infos().
info(MQ) -> info(MQ) ->
maps:from_list([{Key, info(Key, MQ)} || Key <- ?INFO_KEYS]). maps:from_list([{Key, info(Key, MQ)} || Key <- ?INFO_KEYS]).
-spec(info(atom(), mqueue()) -> term()). -spec info(atom(), mqueue()) -> term().
info(store_qos0, #mqueue{store_qos0 = True}) -> info(store_qos0, #mqueue{store_qos0 = True}) ->
True; True;
info(max_len, #mqueue{max_len = MaxLen}) -> info(max_len, #mqueue{max_len = MaxLen}) ->
@ -147,25 +153,30 @@ len(#mqueue{len = Len}) -> Len.
max_len(#mqueue{max_len = MaxLen}) -> MaxLen. max_len(#mqueue{max_len = MaxLen}) -> MaxLen.
%% @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.
%% @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}].
%% @doc Enqueue a message. %% @doc Enqueue a message.
-spec(in(message(), mqueue()) -> {maybe(message()), mqueue()}). -spec in(message(), mqueue()) -> {maybe(message()), mqueue()}.
in(Msg = #message{qos = ?QOS_0}, MQ = #mqueue{store_qos0 = false}) -> in(Msg = #message{qos = ?QOS_0}, MQ = #mqueue{store_qos0 = false}) ->
{_Dropped = Msg, MQ}; {_Dropped = Msg, MQ};
in(Msg = #message{topic = Topic}, MQ = #mqueue{default_p = Dp, in(
p_table = PTab, Msg = #message{topic = Topic},
q = Q, MQ =
len = Len, #mqueue{
max_len = MaxLen, default_p = Dp,
dropped = Dropped p_table = PTab,
} = MQ) -> q = Q,
len = Len,
max_len = MaxLen,
dropped = Dropped
} = MQ
) ->
Priority = get_priority(Topic, PTab, Dp), Priority = get_priority(Topic, PTab, Dp),
PLen = ?PQUEUE:plen(Priority, Q), PLen = ?PQUEUE:plen(Priority, Q),
case MaxLen =/= ?MAX_LEN_INFINITY andalso PLen =:= MaxLen of case MaxLen =/= ?MAX_LEN_INFINITY andalso PLen =:= MaxLen of
@ -178,24 +189,26 @@ in(Msg = #message{topic = Topic}, MQ = #mqueue{default_p = Dp,
{_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.
-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 %% assert, in this case, ?PQUEUE:len should be very cheap
0 = ?PQUEUE:len(Q),
{empty, MQ}; {empty, MQ};
out(MQ = #mqueue{q = Q, len = Len, last_prio = undefined, shift_opts = ShiftOpts}) -> out(MQ = #mqueue{q = Q, len = Len, last_prio = undefined, shift_opts = ShiftOpts}) ->
{{value, Val, Prio}, Q1} = ?PQUEUE:out_p(Q), %% Shouldn't fail, since we've checked the length %% Shouldn't fail, since we've checked the length
{{value, Val, Prio}, Q1} = ?PQUEUE:out_p(Q),
MQ1 = MQ#mqueue{ MQ1 = MQ#mqueue{
q = Q1, q = Q1,
len = Len - 1, len = Len - 1,
last_prio = Prio, last_prio = Prio,
p_credit = get_credits(Prio, ShiftOpts) p_credit = get_credits(Prio, ShiftOpts)
}, },
{{value, Val}, MQ1}; {{value, Val}, MQ1};
out(MQ = #mqueue{q = Q, p_credit = 0}) -> out(MQ = #mqueue{q = Q, p_credit = 0}) ->
MQ1 = MQ#mqueue{ MQ1 = MQ#mqueue{
q = ?PQUEUE:shift(Q), q = ?PQUEUE:shift(Q),
last_prio = undefined last_prio = undefined
}, },
out(MQ1); out(MQ1);
out(MQ = #mqueue{q = Q, len = Len, p_credit = Cnt}) -> out(MQ = #mqueue{q = Q, len = Len, p_credit = Cnt}) ->
{R, Q1} = ?PQUEUE:out(Q), {R, Q1} = ?PQUEUE:out(Q),
@ -232,23 +245,25 @@ get_shift_opt(Opts) ->
%% overhead of ?PQUEUE:rotate %% overhead of ?PQUEUE:rotate
Mult = maps:get(shift_multiplier, Opts, 10), Mult = maps:get(shift_multiplier, Opts, 10),
true = is_integer(Mult) andalso Mult > 0, true = is_integer(Mult) andalso Mult > 0,
Min = case Opts of Min =
#{p_table := PTab} -> case Opts of
case maps:size(PTab) of #{p_table := PTab} ->
0 -> 0; case maps:size(PTab) of
_ -> lists:min(maps:values(PTab)) 0 -> 0;
end; _ -> lists:min(maps:values(PTab))
_ -> end;
?LOWEST_PRIORITY _ ->
end, ?LOWEST_PRIORITY
end,
%% `mqueue' module supports negative priorities, but we don't want %% `mqueue' module supports negative priorities, but we don't want
%% the counter to be negative, so all priorities should be shifted %% the counter to be negative, so all priorities should be shifted
%% by a constant, if negative priorities are used: %% by a constant, if negative priorities are used:
Base = case Min < 0 of Base =
true -> -Min; case Min < 0 of
false -> 0 true -> -Min;
end, false -> 0
end,
#shift_opts{ #shift_opts{
multiplier = Mult, multiplier = Mult,
base = Base base = Base
}. }.

View File

@ -17,14 +17,16 @@
%% Collection of functions for creating node dumps %% Collection of functions for creating node dumps
-module(emqx_node_dump). -module(emqx_node_dump).
-export([ sys_info/0 -export([
, app_env_dump/0 sys_info/0,
]). app_env_dump/0
]).
sys_info() -> sys_info() ->
#{ release => emqx_app:get_release() #{
, otp_version => emqx_vm:get_otp_version() release => emqx_app:get_release(),
}. otp_version => emqx_vm:get_otp_version()
}.
app_env_dump() -> app_env_dump() ->
censor(ets:tab2list(ac_tab)). censor(ets:tab2list(ac_tab)).
@ -37,13 +39,13 @@ censor([_ | Rest]) ->
censor(Rest). censor(Rest).
censor(Path, {Key, Val}) when is_atom(Key) -> censor(Path, {Key, Val}) when is_atom(Key) ->
{Key, censor([Key|Path], Val)}; {Key, censor([Key | Path], Val)};
censor(Path, M) when is_map(M) -> censor(Path, M) when is_map(M) ->
Fun = fun(Key, Val) -> Fun = fun(Key, Val) ->
censor([Key|Path], Val) censor([Key | Path], Val)
end, end,
maps:map(Fun, M); maps:map(Fun, M);
censor(Path, L = [Fst|_]) when is_tuple(Fst) -> censor(Path, L = [Fst | _]) when is_tuple(Fst) ->
[censor(Path, I) || I <- L]; [censor(Path, I) || I <- L];
censor([Key | _], Val) -> censor([Key | _], Val) ->
case is_sensitive(Key) of case is_sensitive(Key) of
@ -58,12 +60,14 @@ is_sensitive(Key) when is_list(Key) ->
Bin -> Bin ->
is_sensitive(Bin) is_sensitive(Bin)
catch catch
_ : _ -> _:_ ->
false false
end; end;
is_sensitive(Key) when is_binary(Key) -> is_sensitive(Key) when is_binary(Key) ->
lists:any(fun(Pattern) -> re:run(Key, Pattern) =/= nomatch end, lists:any(
["passwd", "password", "secret"]); fun(Pattern) -> re:run(Key, Pattern) =/= nomatch end,
["passwd", "password", "secret"]
);
is_sensitive(Key) when is_tuple(Key) -> is_sensitive(Key) when is_tuple(Key) ->
false. false.
@ -77,11 +81,14 @@ obfuscate_value(_Val) ->
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
censor_test() -> censor_test() ->
?assertMatch( [{{env, emqx, listeners}, #{password := <<"********">>}}] ?assertMatch(
, censor([foo, {{env, emqx, listeners}, #{password => <<"secret">>}}, {app, bar}]) [{{env, emqx, listeners}, #{password := <<"********">>}}],
), censor([foo, {{env, emqx, listeners}, #{password => <<"secret">>}}, {app, bar}])
?assertMatch( [{{env, emqx, listeners}, [{foo, 1}, {password, "********"}]}] ),
, censor([{{env, emqx, listeners}, [{foo, 1}, {password, "secret"}]}]) ?assertMatch(
). [{{env, emqx, listeners}, [{foo, 1}, {password, "********"}]}],
censor([{{env, emqx, listeners}, [{foo, 1}, {password, "secret"}]}])
).
-endif. %% TEST %% TEST
-endif.

View File

@ -17,38 +17,39 @@
-include_lib("lc/include/lc.hrl"). -include_lib("lc/include/lc.hrl").
-export([ is_overloaded/0 -export([
, backoff/1 is_overloaded/0,
, backoff_gc/1 backoff/1,
, backoff_hibernation/1 backoff_gc/1,
, backoff_new_conn/1 backoff_hibernation/1,
]). backoff_new_conn/1
]).
%% exports for O&M %% exports for O&M
-export([ status/0 -export([
, enable/0 status/0,
, disable/0 enable/0,
]). disable/0
]).
-type cfg_key() :: -type cfg_key() ::
backoff_gc | backoff_gc
backoff_hibernation | | backoff_hibernation
backoff_new_conn. | backoff_new_conn.
-type cnt_name() :: -type cnt_name() ::
'olp.delay.ok' | 'olp.delay.ok'
'olp.delay.timeout' | | 'olp.delay.timeout'
'olp.hbn' | | 'olp.hbn'
'olp.gc' | | 'olp.gc'
'olp.new_conn'. | 'olp.new_conn'.
-define(overload_protection, overload_protection). -define(overload_protection, overload_protection).
%% @doc Light realtime check if system is overloaded. %% @doc Light realtime check if system is overloaded.
-spec is_overloaded() -> boolean(). -spec is_overloaded() -> boolean().
is_overloaded() -> is_overloaded() ->
load_ctl:is_overloaded(). load_ctl:is_overloaded().
%% @doc Backoff with a delay if the system is overloaded, for tasks that could be deferred. %% @doc Backoff with a delay if the system is overloaded, for tasks that could be deferred.
%% returns `false' if backoff didn't happen, the system is cool. %% returns `false' if backoff didn't happen, the system is cool.
@ -56,78 +57,79 @@ is_overloaded() ->
%% returns `timeout' if backoff is triggered but get unblocked due to timeout as configured. %% returns `timeout' if backoff is triggered but get unblocked due to timeout as configured.
-spec backoff(Zone :: atom()) -> ok | false | timeout. -spec backoff(Zone :: atom()) -> ok | false | timeout.
backoff(Zone) -> backoff(Zone) ->
case emqx_config:get_zone_conf(Zone, [?overload_protection]) of case emqx_config:get_zone_conf(Zone, [?overload_protection]) of
#{enable := true, backoff_delay := Delay} -> #{enable := true, backoff_delay := Delay} ->
case load_ctl:maydelay(Delay) of case load_ctl:maydelay(Delay) of
false -> false; false ->
ok -> false;
emqx_metrics:inc('olp.delay.ok'), ok ->
ok; emqx_metrics:inc('olp.delay.ok'),
timeout -> ok;
emqx_metrics:inc('olp.delay.timeout'), timeout ->
timeout emqx_metrics:inc('olp.delay.timeout'),
end; timeout
_ -> end;
ok _ ->
end. ok
end.
%% @doc If forceful GC should be skipped when the system is overloaded. %% @doc If forceful GC should be skipped when the system is overloaded.
-spec backoff_gc(Zone :: atom()) -> boolean(). -spec backoff_gc(Zone :: atom()) -> boolean().
backoff_gc(Zone) -> backoff_gc(Zone) ->
do_check(Zone, ?FUNCTION_NAME, 'olp.gc'). do_check(Zone, ?FUNCTION_NAME, 'olp.gc').
%% @doc If hibernation should be skipped when the system is overloaded. %% @doc If hibernation should be skipped when the system is overloaded.
-spec backoff_hibernation(Zone :: atom()) -> boolean(). -spec backoff_hibernation(Zone :: atom()) -> boolean().
backoff_hibernation(Zone) -> backoff_hibernation(Zone) ->
do_check(Zone, ?FUNCTION_NAME, 'olp.hbn'). do_check(Zone, ?FUNCTION_NAME, 'olp.hbn').
%% @doc Returns {error, overloaded} if new connection should be %% @doc Returns {error, overloaded} if new connection should be
%% closed when system is overloaded. %% closed when system is overloaded.
-spec backoff_new_conn(Zone :: atom()) -> ok | {error, overloaded}. -spec backoff_new_conn(Zone :: atom()) -> ok | {error, overloaded}.
backoff_new_conn(Zone) -> backoff_new_conn(Zone) ->
case do_check(Zone, ?FUNCTION_NAME, 'olp.new_conn') of case do_check(Zone, ?FUNCTION_NAME, 'olp.new_conn') of
true -> true ->
{error, overloaded}; {error, overloaded};
false -> false ->
ok ok
end. end.
-spec status() -> any(). -spec status() -> any().
status() -> status() ->
is_overloaded(). is_overloaded().
%% @doc turn off background runq check. %% @doc turn off background runq check.
-spec disable() -> ok | {error, timeout}. -spec disable() -> ok | {error, timeout}.
disable() -> disable() ->
load_ctl:stop_runq_flagman(5000). load_ctl:stop_runq_flagman(5000).
%% @doc turn on background runq check. %% @doc turn on background runq check.
-spec enable() -> {ok, pid()} | {error, running | restarting | disabled}. -spec enable() -> {ok, pid()} | {error, running | restarting | disabled}.
enable() -> enable() ->
case load_ctl:restart_runq_flagman() of case load_ctl:restart_runq_flagman() of
{error, disabled} -> {error, disabled} ->
OldCfg = load_ctl:get_config(), OldCfg = load_ctl:get_config(),
ok = load_ctl:put_config(OldCfg#{ ?RUNQ_MON_F0 => true }), ok = load_ctl:put_config(OldCfg#{?RUNQ_MON_F0 => true}),
load_ctl:restart_runq_flagman(); load_ctl:restart_runq_flagman();
Other -> Other ->
Other Other
end. end.
%%% Internals %%% Internals
-spec do_check(Zone::atom(), cfg_key(), cnt_name()) -> boolean(). -spec do_check(Zone :: atom(), cfg_key(), cnt_name()) -> boolean().
do_check(Zone, Key, CntName) -> do_check(Zone, Key, CntName) ->
case load_ctl:is_overloaded() of case load_ctl:is_overloaded() of
true -> true ->
case emqx_config:get_zone_conf(Zone, [?overload_protection]) of case emqx_config:get_zone_conf(Zone, [?overload_protection]) of
#{enable := true, Key := true} -> #{enable := true, Key := true} ->
emqx_metrics:inc(CntName), emqx_metrics:inc(CntName),
true; true;
_ -> _ ->
false false
end; end;
false -> false false ->
end. false
end.
%%%_* Emacs ==================================================================== %%%_* Emacs ====================================================================
%%% Local Variables: %%% Local Variables:

View File

@ -20,25 +20,26 @@
-include("logger.hrl"). -include("logger.hrl").
-export([start_link/0]). -export([start_link/0]).
-export([ get_mem_check_interval/0 -export([
, set_mem_check_interval/1 get_mem_check_interval/0,
, get_sysmem_high_watermark/0 set_mem_check_interval/1,
, set_sysmem_high_watermark/1 get_sysmem_high_watermark/0,
, get_procmem_high_watermark/0 set_sysmem_high_watermark/1,
, set_procmem_high_watermark/1 get_procmem_high_watermark/0,
]). set_procmem_high_watermark/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-include("emqx.hrl"). -include("emqx.hrl").
@ -93,41 +94,47 @@ handle_call(Req, _From, State) ->
{reply, {error, {unexpected_call, Req}}, State}. {reply, {error, {unexpected_call, Req}}, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast=> Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
handle_info({timeout, _Timer, check}, State) -> handle_info({timeout, _Timer, check}, State) ->
CPUHighWatermark = emqx:get_config([sysmon, os, cpu_high_watermark]) * 100, CPUHighWatermark = emqx:get_config([sysmon, os, cpu_high_watermark]) * 100,
CPULowWatermark = emqx:get_config([sysmon, os, cpu_low_watermark]) * 100, CPULowWatermark = emqx:get_config([sysmon, os, cpu_low_watermark]) * 100,
_ = case emqx_vm:cpu_util() of %% TODO: should be improved? %% TODO: should be improved?
0 -> ok; _ =
Busy when Busy > CPUHighWatermark -> case emqx_vm:cpu_util() of
Usage = list_to_binary(io_lib:format("~.2f%", [Busy])), 0 ->
Message = <<Usage/binary, " cpu usage">>, ok;
emqx_alarm:activate(high_cpu_usage, Busy when Busy > CPUHighWatermark ->
#{ Usage = list_to_binary(io_lib:format("~.2f%", [Busy])),
usage => Usage, Message = <<Usage/binary, " cpu usage">>,
high_watermark => CPUHighWatermark, emqx_alarm:activate(
low_watermark => CPULowWatermark high_cpu_usage,
}, #{
Message), usage => Usage,
start_check_timer(); high_watermark => CPUHighWatermark,
Busy when Busy < CPULowWatermark -> low_watermark => CPULowWatermark
Usage = list_to_binary(io_lib:format("~.2f%", [Busy])), },
Message = <<Usage/binary, " cpu usage">>, Message
emqx_alarm:deactivate(high_cpu_usage, ),
#{ start_check_timer();
usage => Usage, Busy when Busy < CPULowWatermark ->
high_watermark => CPUHighWatermark, Usage = list_to_binary(io_lib:format("~.2f%", [Busy])),
low_watermark => CPULowWatermark Message = <<Usage/binary, " cpu usage">>,
}, emqx_alarm:deactivate(
Message), high_cpu_usage,
start_check_timer(); #{
_Busy -> usage => Usage,
start_check_timer() high_watermark => CPUHighWatermark,
end, low_watermark => CPULowWatermark
},
Message
),
start_check_timer();
_Busy ->
start_check_timer()
end,
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -157,10 +164,11 @@ start_check_timer() ->
%% so it can only be checked again at startup. %% so it can only be checked again at startup.
ensure_system_memory_alarm(HW) -> ensure_system_memory_alarm(HW) ->
case erlang:whereis(memsup) of case erlang:whereis(memsup) of
undefined -> ok; undefined ->
ok;
_Pid -> _Pid ->
{Total, Allocated, _Worst} = memsup:get_memory_data(), {Total, Allocated, _Worst} = memsup:get_memory_data(),
case Total =/= 0 andalso Allocated/Total * 100 > HW of case Total =/= 0 andalso Allocated / Total * 100 > HW of
true -> emqx_alarm:activate(high_system_memory_usage, #{high_watermark => HW}); true -> emqx_alarm:activate(high_system_memory_usage, #{high_watermark => HW});
false -> ok false -> ok
end end

View File

@ -20,89 +20,83 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
%% Header APIs %% Header APIs
-export([ type/1 -export([
, type_name/1 type/1,
, dup/1 type_name/1,
, qos/1 dup/1,
, retain/1 qos/1,
]). retain/1
]).
%% Field APIs %% Field APIs
-export([ proto_name/1 -export([
, proto_ver/1 proto_name/1,
, info/2 proto_ver/1,
, set_props/2 info/2,
]). set_props/2
]).
%% Check API %% Check API
-export([ check/1 -export([
, check/2 check/1,
]). check/2
]).
-export([ to_message/2 -export([
, to_message/3 to_message/2,
, will_msg/1 to_message/3,
]). will_msg/1
]).
-export([ format/1 -export([
, format/2 format/1,
]). format/2
]).
-export([encode_hex/1]). -export([encode_hex/1]).
-define(TYPE_NAMES, -define(TYPE_NAMES,
{ 'CONNECT' {'CONNECT', 'CONNACK', 'PUBLISH', 'PUBACK', 'PUBREC', 'PUBREL', 'PUBCOMP', 'SUBSCRIBE',
, 'CONNACK' 'SUBACK', 'UNSUBSCRIBE', 'UNSUBACK', 'PINGREQ', 'PINGRESP', 'DISCONNECT', 'AUTH'}
, 'PUBLISH' ).
, 'PUBACK'
, 'PUBREC'
, 'PUBREL'
, 'PUBCOMP'
, 'SUBSCRIBE'
, 'SUBACK'
, 'UNSUBSCRIBE'
, 'UNSUBACK'
, 'PINGREQ'
, 'PINGRESP'
, 'DISCONNECT'
, 'AUTH'
}).
-type(connect() :: #mqtt_packet_connect{}). -type connect() :: #mqtt_packet_connect{}.
-type(publish() :: #mqtt_packet_publish{}). -type publish() :: #mqtt_packet_publish{}.
-type(subscribe() :: #mqtt_packet_subscribe{}). -type subscribe() :: #mqtt_packet_subscribe{}.
-type(unsubscribe() :: #mqtt_packet_unsubscribe{}). -type unsubscribe() :: #mqtt_packet_unsubscribe{}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Packet Type and Flags. %% MQTT Packet Type and Flags.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc MQTT packet type. %% @doc MQTT packet type.
-spec(type(emqx_types:packet()) -> emqx_types:packet_type()). -spec type(emqx_types:packet()) -> emqx_types:packet_type().
type(#mqtt_packet{header = #mqtt_packet_header{type = Type}}) -> type(#mqtt_packet{header = #mqtt_packet_header{type = Type}}) ->
Type. Type.
%% @doc Name of MQTT packet type. %% @doc Name of MQTT packet type.
-spec(type_name(emqx_types:packet() | non_neg_integer()) -> atom() | string()). -spec type_name(emqx_types:packet() | non_neg_integer()) -> atom() | string().
type_name(#mqtt_packet{} = Packet) -> type_name(#mqtt_packet{} = Packet) ->
type_name(type(Packet)); type_name(type(Packet));
type_name(0) -> 'FORBIDDEN'; type_name(0) ->
'FORBIDDEN';
type_name(Type) when Type > 0 andalso Type =< tuple_size(?TYPE_NAMES) -> type_name(Type) when Type > 0 andalso Type =< tuple_size(?TYPE_NAMES) ->
element(Type, ?TYPE_NAMES); element(Type, ?TYPE_NAMES);
type_name(Type) -> "UNKNOWN("++ integer_to_list(Type) ++")". type_name(Type) ->
"UNKNOWN(" ++ integer_to_list(Type) ++ ")".
%% @doc Dup flag of MQTT packet. %% @doc Dup flag of MQTT packet.
-spec(dup(emqx_types:packet()) -> boolean()). -spec dup(emqx_types:packet()) -> boolean().
dup(#mqtt_packet{header = #mqtt_packet_header{dup = Dup}}) -> dup(#mqtt_packet{header = #mqtt_packet_header{dup = Dup}}) ->
Dup. Dup.
%% @doc QoS of MQTT packet type. %% @doc QoS of MQTT packet type.
-spec(qos(emqx_types:packet()) -> emqx_types:qos()). -spec qos(emqx_types:packet()) -> emqx_types:qos().
qos(#mqtt_packet{header = #mqtt_packet_header{qos = QoS}}) -> qos(#mqtt_packet{header = #mqtt_packet_header{qos = QoS}}) ->
QoS. QoS.
%% @doc Retain flag of MQTT packet. %% @doc Retain flag of MQTT packet.
-spec(retain(emqx_types:packet()) -> boolean()). -spec retain(emqx_types:packet()) -> boolean().
retain(#mqtt_packet{header = #mqtt_packet_header{retain = Retain}}) -> retain(#mqtt_packet{header = #mqtt_packet_header{retain = Retain}}) ->
Retain. Retain.
@ -111,14 +105,14 @@ retain(#mqtt_packet{header = #mqtt_packet_header{retain = Retain}}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Protocol name of the CONNECT Packet. %% @doc Protocol name of the CONNECT Packet.
-spec(proto_name(emqx_types:packet()|connect()) -> binary()). -spec proto_name(emqx_types:packet() | connect()) -> binary().
proto_name(?CONNECT_PACKET(ConnPkt)) -> proto_name(?CONNECT_PACKET(ConnPkt)) ->
proto_name(ConnPkt); proto_name(ConnPkt);
proto_name(#mqtt_packet_connect{proto_name = Name}) -> proto_name(#mqtt_packet_connect{proto_name = Name}) ->
Name. Name.
%% @doc Protocol version of the CONNECT Packet. %% @doc Protocol version of the CONNECT Packet.
-spec(proto_ver(emqx_types:packet()|connect()) -> emqx_types:proto_ver()). -spec proto_ver(emqx_types:packet() | connect()) -> emqx_types:proto_ver().
proto_ver(?CONNECT_PACKET(ConnPkt)) -> proto_ver(?CONNECT_PACKET(ConnPkt)) ->
proto_ver(ConnPkt); proto_ver(ConnPkt);
proto_ver(#mqtt_packet_connect{proto_ver = Ver}) -> proto_ver(#mqtt_packet_connect{proto_ver = Ver}) ->
@ -158,61 +152,52 @@ info(username, #mqtt_packet_connect{username = Username}) ->
Username; Username;
info(password, #mqtt_packet_connect{password = Password}) -> info(password, #mqtt_packet_connect{password = Password}) ->
Password; Password;
info(ack_flags, #mqtt_packet_connack{ack_flags = Flags}) -> info(ack_flags, #mqtt_packet_connack{ack_flags = Flags}) ->
Flags; Flags;
info(reason_code, #mqtt_packet_connack{reason_code = RC}) -> info(reason_code, #mqtt_packet_connack{reason_code = RC}) ->
RC; RC;
info(properties, #mqtt_packet_connack{properties = Props}) -> info(properties, #mqtt_packet_connack{properties = Props}) ->
Props; Props;
info(topic_name, #mqtt_packet_publish{topic_name = Topic}) -> info(topic_name, #mqtt_packet_publish{topic_name = Topic}) ->
Topic; Topic;
info(packet_id, #mqtt_packet_publish{packet_id = PacketId}) -> info(packet_id, #mqtt_packet_publish{packet_id = PacketId}) ->
PacketId; PacketId;
info(properties, #mqtt_packet_publish{properties = Props}) -> info(properties, #mqtt_packet_publish{properties = Props}) ->
Props; Props;
info(packet_id, #mqtt_packet_puback{packet_id = PacketId}) -> info(packet_id, #mqtt_packet_puback{packet_id = PacketId}) ->
PacketId; PacketId;
info(reason_code, #mqtt_packet_puback{reason_code = RC}) -> info(reason_code, #mqtt_packet_puback{reason_code = RC}) ->
RC; RC;
info(properties, #mqtt_packet_puback{properties = Props}) -> info(properties, #mqtt_packet_puback{properties = Props}) ->
Props; Props;
info(packet_id, #mqtt_packet_subscribe{packet_id = PacketId}) -> info(packet_id, #mqtt_packet_subscribe{packet_id = PacketId}) ->
PacketId; PacketId;
info(properties, #mqtt_packet_subscribe{properties = Props}) -> info(properties, #mqtt_packet_subscribe{properties = Props}) ->
Props; Props;
info(topic_filters, #mqtt_packet_subscribe{topic_filters = Topics}) -> info(topic_filters, #mqtt_packet_subscribe{topic_filters = Topics}) ->
Topics; Topics;
info(packet_id, #mqtt_packet_suback{packet_id = PacketId}) -> info(packet_id, #mqtt_packet_suback{packet_id = PacketId}) ->
PacketId; PacketId;
info(properties, #mqtt_packet_suback{properties = Props}) -> info(properties, #mqtt_packet_suback{properties = Props}) ->
Props; Props;
info(reason_codes, #mqtt_packet_suback{reason_codes = RCs}) -> info(reason_codes, #mqtt_packet_suback{reason_codes = RCs}) ->
RCs; RCs;
info(packet_id, #mqtt_packet_unsubscribe{packet_id = PacketId}) -> info(packet_id, #mqtt_packet_unsubscribe{packet_id = PacketId}) ->
PacketId; PacketId;
info(properties, #mqtt_packet_unsubscribe{properties = Props}) -> info(properties, #mqtt_packet_unsubscribe{properties = Props}) ->
Props; Props;
info(topic_filters, #mqtt_packet_unsubscribe{topic_filters = Topics}) -> info(topic_filters, #mqtt_packet_unsubscribe{topic_filters = Topics}) ->
Topics; Topics;
info(packet_id, #mqtt_packet_unsuback{packet_id = PacketId}) -> info(packet_id, #mqtt_packet_unsuback{packet_id = PacketId}) ->
PacketId; PacketId;
info(properties, #mqtt_packet_unsuback{properties = Props}) -> info(properties, #mqtt_packet_unsuback{properties = Props}) ->
Props; Props;
info(reason_codes, #mqtt_packet_unsuback{reason_codes = RCs}) -> info(reason_codes, #mqtt_packet_unsuback{reason_codes = RCs}) ->
RCs; RCs;
info(reason_code, #mqtt_packet_disconnect{reason_code = RC}) -> info(reason_code, #mqtt_packet_disconnect{reason_code = RC}) ->
RC; RC;
info(properties, #mqtt_packet_disconnect{properties = Props}) -> info(properties, #mqtt_packet_disconnect{properties = Props}) ->
Props; Props;
info(reason_code, #mqtt_packet_auth{reason_code = RC}) -> info(reason_code, #mqtt_packet_auth{reason_code = RC}) ->
RC; RC;
info(properties, #mqtt_packet_auth{properties = Props}) -> info(properties, #mqtt_packet_auth{properties = Props}) ->
@ -220,31 +205,22 @@ info(properties, #mqtt_packet_auth{properties = Props}) ->
set_props(Props, #mqtt_packet_connect{} = Pkt) -> set_props(Props, #mqtt_packet_connect{} = Pkt) ->
Pkt#mqtt_packet_connect{properties = Props}; Pkt#mqtt_packet_connect{properties = Props};
set_props(Props, #mqtt_packet_connack{} = Pkt) -> set_props(Props, #mqtt_packet_connack{} = Pkt) ->
Pkt#mqtt_packet_connack{properties = Props}; Pkt#mqtt_packet_connack{properties = Props};
set_props(Props, #mqtt_packet_publish{} = Pkt) -> set_props(Props, #mqtt_packet_publish{} = Pkt) ->
Pkt#mqtt_packet_publish{properties = Props}; Pkt#mqtt_packet_publish{properties = Props};
set_props(Props, #mqtt_packet_puback{} = Pkt) -> set_props(Props, #mqtt_packet_puback{} = Pkt) ->
Pkt#mqtt_packet_puback{properties = Props}; Pkt#mqtt_packet_puback{properties = Props};
set_props(Props, #mqtt_packet_subscribe{} = Pkt) -> set_props(Props, #mqtt_packet_subscribe{} = Pkt) ->
Pkt#mqtt_packet_subscribe{properties = Props}; Pkt#mqtt_packet_subscribe{properties = Props};
set_props(Props, #mqtt_packet_suback{} = Pkt) -> set_props(Props, #mqtt_packet_suback{} = Pkt) ->
Pkt#mqtt_packet_suback{properties = Props}; Pkt#mqtt_packet_suback{properties = Props};
set_props(Props, #mqtt_packet_unsubscribe{} = Pkt) -> set_props(Props, #mqtt_packet_unsubscribe{} = Pkt) ->
Pkt#mqtt_packet_unsubscribe{properties = Props}; Pkt#mqtt_packet_unsubscribe{properties = Props};
set_props(Props, #mqtt_packet_unsuback{} = Pkt) -> set_props(Props, #mqtt_packet_unsuback{} = Pkt) ->
Pkt#mqtt_packet_unsuback{properties = Props}; Pkt#mqtt_packet_unsuback{properties = Props};
set_props(Props, #mqtt_packet_disconnect{} = Pkt) -> set_props(Props, #mqtt_packet_disconnect{} = Pkt) ->
Pkt#mqtt_packet_disconnect{properties = Props}; Pkt#mqtt_packet_disconnect{properties = Props};
set_props(Props, #mqtt_packet_auth{} = Pkt) -> set_props(Props, #mqtt_packet_auth{} = Pkt) ->
Pkt#mqtt_packet_auth{properties = Props}. Pkt#mqtt_packet_auth{properties = Props}.
@ -253,10 +229,12 @@ set_props(Props, #mqtt_packet_auth{} = Pkt) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Check PubSub Packet. %% @doc Check PubSub Packet.
-spec(check(emqx_types:packet()|publish()|subscribe()|unsubscribe()) -spec check(emqx_types:packet() | publish() | subscribe() | unsubscribe()) ->
-> ok | {error, emqx_types:reason_code()}). ok | {error, emqx_types:reason_code()}.
check(#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH}, check(#mqtt_packet{
variable = PubPkt}) when not is_tuple(PubPkt) -> header = #mqtt_packet_header{type = ?PUBLISH},
variable = PubPkt
}) when not is_tuple(PubPkt) ->
%% publish without any data %% publish without any data
%% disconnect instead of crash %% disconnect instead of crash
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
@ -266,11 +244,10 @@ check(#mqtt_packet{variable = #mqtt_packet_subscribe{} = SubPkt}) ->
check(SubPkt); check(SubPkt);
check(#mqtt_packet{variable = #mqtt_packet_unsubscribe{} = UnsubPkt}) -> check(#mqtt_packet{variable = #mqtt_packet_unsubscribe{} = UnsubPkt}) ->
check(UnsubPkt); check(UnsubPkt);
%% A Topic Alias of 0 is not permitted. %% A Topic Alias of 0 is not permitted.
check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias':= 0}}) -> check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' := 0}}) ->
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias':= _Alias}}) -> check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' := _Alias}}) ->
ok; ok;
check(#mqtt_packet_publish{topic_name = <<>>, properties = #{}}) -> check(#mqtt_packet_publish{topic_name = <<>>, properties = #{}}) ->
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
@ -281,26 +258,24 @@ check(#mqtt_packet_publish{topic_name = TopicName, properties = Props}) ->
error:_Error -> error:_Error ->
{error, ?RC_TOPIC_NAME_INVALID} {error, ?RC_TOPIC_NAME_INVALID}
end; end;
check(#mqtt_packet_subscribe{properties = #{'Subscription-Identifier' := I}}) when
check(#mqtt_packet_subscribe{properties = #{'Subscription-Identifier' := I}}) I =< 0; I >= 16#FFFFFFF
when I =< 0; I >= 16#FFFFFFF -> ->
{error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED}; {error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED};
check(#mqtt_packet_subscribe{topic_filters = []}) -> check(#mqtt_packet_subscribe{topic_filters = []}) ->
{error, ?RC_TOPIC_FILTER_INVALID}; {error, ?RC_TOPIC_FILTER_INVALID};
check(#mqtt_packet_subscribe{topic_filters = TopicFilters}) -> check(#mqtt_packet_subscribe{topic_filters = TopicFilters}) ->
try validate_topic_filters(TopicFilters) try
validate_topic_filters(TopicFilters)
catch catch
error:_Error -> error:_Error ->
{error, ?RC_TOPIC_FILTER_INVALID} {error, ?RC_TOPIC_FILTER_INVALID}
end; end;
check(#mqtt_packet_unsubscribe{topic_filters = []}) -> check(#mqtt_packet_unsubscribe{topic_filters = []}) ->
{error, ?RC_TOPIC_FILTER_INVALID}; {error, ?RC_TOPIC_FILTER_INVALID};
check(#mqtt_packet_unsubscribe{topic_filters = TopicFilters}) -> check(#mqtt_packet_unsubscribe{topic_filters = TopicFilters}) ->
try validate_topic_filters(TopicFilters) try
validate_topic_filters(TopicFilters)
catch catch
error:_Error -> error:_Error ->
{error, ?RC_TOPIC_FILTER_INVALID} {error, ?RC_TOPIC_FILTER_INVALID}
@ -308,10 +283,8 @@ check(#mqtt_packet_unsubscribe{topic_filters = TopicFilters}) ->
check_pub_props(#{'Topic-Alias' := 0}) -> check_pub_props(#{'Topic-Alias' := 0}) ->
{error, ?RC_TOPIC_ALIAS_INVALID}; {error, ?RC_TOPIC_ALIAS_INVALID};
check_pub_props(#{'Subscription-Identifier' := 0}) -> check_pub_props(#{'Subscription-Identifier' := 0}) ->
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
check_pub_props(#{'Response-Topic' := ResponseTopic}) -> check_pub_props(#{'Response-Topic' := ResponseTopic}) ->
try emqx_topic:validate(name, ResponseTopic) of try emqx_topic:validate(name, ResponseTopic) of
true -> ok true -> ok
@ -319,41 +292,70 @@ check_pub_props(#{'Response-Topic' := ResponseTopic}) ->
error:_Error -> error:_Error ->
{error, ?RC_PROTOCOL_ERROR} {error, ?RC_PROTOCOL_ERROR}
end; end;
check_pub_props(_Props) -> ok. check_pub_props(_Props) ->
ok.
%% @doc Check CONNECT Packet. %% @doc Check CONNECT Packet.
-spec(check(emqx_types:packet()|connect(), Opts :: map()) -spec check(emqx_types:packet() | connect(), Opts :: map()) ->
-> ok | {error, emqx_types:reason_code()}). ok | {error, emqx_types:reason_code()}.
check(?CONNECT_PACKET(ConnPkt), Opts) -> check(?CONNECT_PACKET(ConnPkt), Opts) ->
check(ConnPkt, Opts); check(ConnPkt, Opts);
check(ConnPkt, Opts) when is_record(ConnPkt, mqtt_packet_connect) -> check(ConnPkt, Opts) when is_record(ConnPkt, mqtt_packet_connect) ->
run_checks([fun check_proto_ver/2, run_checks(
fun check_client_id/2, [
fun check_conn_props/2, fun check_proto_ver/2,
fun check_will_msg/2], ConnPkt, Opts). fun check_client_id/2,
fun check_conn_props/2,
fun check_will_msg/2
],
ConnPkt,
Opts
).
check_proto_ver(#mqtt_packet_connect{proto_ver = Ver, check_proto_ver(
proto_name = Name}, _Opts) -> #mqtt_packet_connect{
proto_ver = Ver,
proto_name = Name
},
_Opts
) ->
case proplists:get_value(Ver, ?PROTOCOL_NAMES) of case proplists:get_value(Ver, ?PROTOCOL_NAMES) of
Name -> ok; Name -> ok;
_Other -> {error, ?RC_UNSUPPORTED_PROTOCOL_VERSION} _Other -> {error, ?RC_UNSUPPORTED_PROTOCOL_VERSION}
end. end.
%% MQTT3.1 does not allow null clientId %% MQTT3.1 does not allow null clientId
check_client_id(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, check_client_id(
clientid = <<>>}, _Opts) -> #mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V3,
clientid = <<>>
},
_Opts
) ->
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}; {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID};
%% Issue#599: Null clientId and clean_start = false %% Issue#599: Null clientId and clean_start = false
check_client_id(#mqtt_packet_connect{clientid = <<>>, check_client_id(
clean_start = false}, _Opts) -> #mqtt_packet_connect{
clientid = <<>>,
clean_start = false
},
_Opts
) ->
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}; {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID};
check_client_id(#mqtt_packet_connect{clientid = <<>>, check_client_id(
clean_start = true}, _Opts) -> #mqtt_packet_connect{
clientid = <<>>,
clean_start = true
},
_Opts
) ->
ok; ok;
check_client_id(#mqtt_packet_connect{clientid = ClientId}, check_client_id(
#{max_clientid_len := MaxLen} = _Opts) -> #mqtt_packet_connect{clientid = ClientId},
#{max_clientid_len := MaxLen} = _Opts
) ->
case (1 =< (Len = byte_size(ClientId))) andalso (Len =< MaxLen) of case (1 =< (Len = byte_size(ClientId))) andalso (Len =< MaxLen) of
true -> ok; true -> ok;
false -> {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} false -> {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}
end. end.
@ -361,32 +363,44 @@ check_conn_props(#mqtt_packet_connect{properties = undefined}, _Opts) ->
ok; ok;
check_conn_props(#mqtt_packet_connect{properties = #{'Receive-Maximum' := 0}}, _Opts) -> check_conn_props(#mqtt_packet_connect{properties = #{'Receive-Maximum' := 0}}, _Opts) ->
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
check_conn_props(#mqtt_packet_connect{properties = #{'Request-Response-Information' := ReqRespInfo}}, _Opts) check_conn_props(
when ReqRespInfo =/= 0, ReqRespInfo =/= 1 -> #mqtt_packet_connect{properties = #{'Request-Response-Information' := ReqRespInfo}}, _Opts
) when
ReqRespInfo =/= 0, ReqRespInfo =/= 1
->
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
check_conn_props(#mqtt_packet_connect{properties = #{'Request-Problem-Information' := ReqProInfo}}, _Opts) check_conn_props(
when ReqProInfo =/= 0, ReqProInfo =/= 1 -> #mqtt_packet_connect{properties = #{'Request-Problem-Information' := ReqProInfo}}, _Opts
) when
ReqProInfo =/= 0, ReqProInfo =/= 1
->
{error, ?RC_PROTOCOL_ERROR}; {error, ?RC_PROTOCOL_ERROR};
check_conn_props(_ConnPkt, _Opts) -> ok. check_conn_props(_ConnPkt, _Opts) ->
ok.
check_will_msg(#mqtt_packet_connect{will_flag = false}, _Caps) -> check_will_msg(#mqtt_packet_connect{will_flag = false}, _Caps) ->
ok; ok;
check_will_msg(#mqtt_packet_connect{will_retain = true}, check_will_msg(
_Opts = #{mqtt_retain_available := false}) -> #mqtt_packet_connect{will_retain = true},
_Opts = #{mqtt_retain_available := false}
) ->
{error, ?RC_RETAIN_NOT_SUPPORTED}; {error, ?RC_RETAIN_NOT_SUPPORTED};
check_will_msg(#mqtt_packet_connect{will_qos = WillQoS}, check_will_msg(
_Opts = #{max_qos_allowed := MaxQoS}) when WillQoS > MaxQoS -> #mqtt_packet_connect{will_qos = WillQoS},
_Opts = #{max_qos_allowed := MaxQoS}
) when WillQoS > MaxQoS ->
{error, ?RC_QOS_NOT_SUPPORTED}; {error, ?RC_QOS_NOT_SUPPORTED};
check_will_msg(#mqtt_packet_connect{will_topic = WillTopic}, _Opts) -> check_will_msg(#mqtt_packet_connect{will_topic = WillTopic}, _Opts) ->
try emqx_topic:validate(name, WillTopic) of try emqx_topic:validate(name, WillTopic) of
true -> ok true -> ok
catch error:_Error -> catch
{error, ?RC_TOPIC_NAME_INVALID} error:_Error ->
{error, ?RC_TOPIC_NAME_INVALID}
end. end.
run_checks([], _Packet, _Options) -> run_checks([], _Packet, _Options) ->
ok; ok;
run_checks([Check|More], Packet, Options) -> run_checks([Check | More], Packet, Options) ->
case Check(Packet, Options) of case Check(Packet, Options) of
ok -> run_checks(More, Packet, Options); ok -> run_checks(More, Packet, Options);
Error = {error, _Reason} -> Error Error = {error, _Reason} -> Error
@ -396,134 +410,192 @@ run_checks([Check|More], Packet, Options) ->
%% @private %% @private
validate_topic_filters(TopicFilters) -> validate_topic_filters(TopicFilters) ->
lists:foreach( lists:foreach(
fun({TopicFilter, _SubOpts}) -> fun
emqx_topic:validate(TopicFilter); ({TopicFilter, _SubOpts}) ->
(TopicFilter) -> emqx_topic:validate(TopicFilter);
emqx_topic:validate(TopicFilter) (TopicFilter) ->
end, TopicFilters). emqx_topic:validate(TopicFilter)
end,
TopicFilters
).
-spec(to_message(emqx_types:packet(), emqx_types:clientid()) -> emqx_types:message()). -spec to_message(emqx_types:packet(), emqx_types:clientid()) -> emqx_types:message().
to_message(Packet, ClientId) -> to_message(Packet, ClientId) ->
to_message(Packet, ClientId, #{}). to_message(Packet, ClientId, #{}).
%% @doc Transform Publish Packet to Message. %% @doc Transform Publish Packet to Message.
-spec(to_message(emqx_types:packet(), emqx_types:clientid(), map()) -> emqx_types:message()). -spec to_message(emqx_types:packet(), emqx_types:clientid(), map()) -> emqx_types:message().
to_message(#mqtt_packet{ to_message(
header = #mqtt_packet_header{ #mqtt_packet{
type = ?PUBLISH, header = #mqtt_packet_header{
retain = Retain, type = ?PUBLISH,
qos = QoS, retain = Retain,
dup = Dup}, qos = QoS,
variable = #mqtt_packet_publish{ dup = Dup
topic_name = Topic, },
properties = Props}, variable = #mqtt_packet_publish{
payload = Payload topic_name = Topic,
}, ClientId, Headers) -> properties = Props
},
payload = Payload
},
ClientId,
Headers
) ->
Msg = emqx_message:make(ClientId, QoS, Topic, Payload), Msg = emqx_message:make(ClientId, QoS, Topic, Payload),
Msg#message{flags = #{dup => Dup, retain => Retain}, Msg#message{
headers = Headers#{properties => Props}}. flags = #{dup => Dup, retain => Retain},
headers = Headers#{properties => Props}
}.
-spec(will_msg(#mqtt_packet_connect{}) -> emqx_types:message()). -spec will_msg(#mqtt_packet_connect{}) -> emqx_types:message().
will_msg(#mqtt_packet_connect{will_flag = false}) -> will_msg(#mqtt_packet_connect{will_flag = false}) ->
undefined; undefined;
will_msg(#mqtt_packet_connect{clientid = ClientId, will_msg(#mqtt_packet_connect{
username = Username, clientid = ClientId,
will_retain = Retain, username = Username,
will_qos = QoS, will_retain = Retain,
will_topic = Topic, will_qos = QoS,
will_props = Props, will_topic = Topic,
will_payload = Payload}) -> will_props = Props,
will_payload = Payload
}) ->
Msg = emqx_message:make(ClientId, QoS, Topic, Payload), Msg = emqx_message:make(ClientId, QoS, Topic, Payload),
Msg#message{flags = #{dup => false, retain => Retain}, Msg#message{
headers = #{username => Username, properties => Props}}. flags = #{dup => false, retain => Retain},
headers = #{username => Username, properties => Props}
}.
%% @doc Format packet %% @doc Format packet
-spec(format(emqx_types:packet()) -> iolist()). -spec format(emqx_types:packet()) -> iolist().
format(Packet) -> format(Packet, emqx_trace_handler:payload_encode()). format(Packet) -> format(Packet, emqx_trace_handler:payload_encode()).
%% @doc Format packet %% @doc Format packet
-spec(format(emqx_types:packet(), hex | text | hidden) -> iolist()). -spec format(emqx_types:packet(), hex | text | hidden) -> iolist().
format(#mqtt_packet{header = Header, variable = Variable, payload = Payload}, PayloadEncode) -> format(#mqtt_packet{header = Header, variable = Variable, payload = Payload}, PayloadEncode) ->
HeaderIO = format_header(Header), HeaderIO = format_header(Header),
case format_variable(Variable, Payload, PayloadEncode) of case format_variable(Variable, Payload, PayloadEncode) of
"" -> HeaderIO; "" -> HeaderIO;
VarIO -> [HeaderIO,",", VarIO] VarIO -> [HeaderIO, ",", VarIO]
end. end.
format_header(#mqtt_packet_header{type = Type, format_header(#mqtt_packet_header{
dup = Dup, type = Type,
qos = QoS, dup = Dup,
retain = Retain}) -> qos = QoS,
retain = Retain
}) ->
io_lib:format("~ts(Q~p, R~p, D~p)", [type_name(Type), QoS, i(Retain), i(Dup)]). io_lib:format("~ts(Q~p, R~p, D~p)", [type_name(Type), QoS, i(Retain), i(Dup)]).
format_variable(undefined, _, _) -> ""; format_variable(undefined, _, _) ->
"";
format_variable(Variable, undefined, PayloadEncode) -> format_variable(Variable, undefined, PayloadEncode) ->
format_variable(Variable, PayloadEncode); format_variable(Variable, PayloadEncode);
format_variable(Variable, Payload, PayloadEncode) -> format_variable(Variable, Payload, PayloadEncode) ->
[format_variable(Variable, PayloadEncode), format_payload(Payload, PayloadEncode)]. [format_variable(Variable, PayloadEncode), format_payload(Payload, PayloadEncode)].
format_variable(#mqtt_packet_connect{ format_variable(
proto_ver = ProtoVer, #mqtt_packet_connect{
proto_name = ProtoName, proto_ver = ProtoVer,
will_retain = WillRetain, proto_name = ProtoName,
will_qos = WillQoS, will_retain = WillRetain,
will_flag = WillFlag, will_qos = WillQoS,
clean_start = CleanStart, will_flag = WillFlag,
keepalive = KeepAlive, clean_start = CleanStart,
clientid = ClientId, keepalive = KeepAlive,
will_topic = WillTopic, clientid = ClientId,
will_payload = WillPayload, will_topic = WillTopic,
username = Username, will_payload = WillPayload,
password = Password}, username = Username,
PayloadEncode) -> password = Password
},
PayloadEncode
) ->
Base = io_lib:format( Base = io_lib:format(
"ClientId=~ts, ProtoName=~ts, ProtoVsn=~p, CleanStart=~ts, KeepAlive=~p, Username=~ts, Password=~ts", "ClientId=~ts, ProtoName=~ts, ProtoVsn=~p, CleanStart=~ts, KeepAlive=~p, Username=~ts, Password=~ts",
[ClientId, ProtoName, ProtoVer, CleanStart, KeepAlive, Username, format_password(Password)]), [ClientId, ProtoName, ProtoVer, CleanStart, KeepAlive, Username, format_password(Password)]
),
case WillFlag of case WillFlag of
true -> true ->
[Base, io_lib:format(", Will(Q~p, R~p, Topic=~ts ", [
[WillQoS, i(WillRetain), WillTopic]), Base,
format_payload(WillPayload, PayloadEncode), ")"]; io_lib:format(
", Will(Q~p, R~p, Topic=~ts ",
[WillQoS, i(WillRetain), WillTopic]
),
format_payload(WillPayload, PayloadEncode),
")"
];
false -> false ->
Base Base
end; end;
format_variable(
format_variable(#mqtt_packet_disconnect #mqtt_packet_disconnect{
{reason_code = ReasonCode}, _) -> reason_code = ReasonCode
},
_
) ->
io_lib:format("ReasonCode=~p", [ReasonCode]); io_lib:format("ReasonCode=~p", [ReasonCode]);
format_variable(
format_variable(#mqtt_packet_connack{ack_flags = AckFlags, #mqtt_packet_connack{
reason_code = ReasonCode}, _) -> ack_flags = AckFlags,
reason_code = ReasonCode
},
_
) ->
io_lib:format("AckFlags=~p, ReasonCode=~p", [AckFlags, ReasonCode]); io_lib:format("AckFlags=~p, ReasonCode=~p", [AckFlags, ReasonCode]);
format_variable(
format_variable(#mqtt_packet_publish{topic_name = TopicName, #mqtt_packet_publish{
packet_id = PacketId}, _) -> topic_name = TopicName,
packet_id = PacketId
},
_
) ->
io_lib:format("Topic=~ts, PacketId=~p", [TopicName, PacketId]); io_lib:format("Topic=~ts, PacketId=~p", [TopicName, PacketId]);
format_variable(
format_variable(#mqtt_packet_puback{packet_id = PacketId, #mqtt_packet_puback{
reason_code = ReasonCode}, _) -> packet_id = PacketId,
reason_code = ReasonCode
},
_
) ->
io_lib:format("PacketId=~p, ReasonCode=~p", [PacketId, ReasonCode]); io_lib:format("PacketId=~p, ReasonCode=~p", [PacketId, ReasonCode]);
format_variable(
format_variable(#mqtt_packet_subscribe{packet_id = PacketId, #mqtt_packet_subscribe{
topic_filters = TopicFilters}, _) -> packet_id = PacketId,
[io_lib:format("PacketId=~p ", [PacketId]), "TopicFilters=", topic_filters = TopicFilters
format_topic_filters(TopicFilters)]; },
_
format_variable(#mqtt_packet_unsubscribe{packet_id = PacketId, ) ->
topic_filters = Topics}, _) -> [
[io_lib:format("PacketId=~p ", [PacketId]), "TopicFilters=", io_lib:format("PacketId=~p ", [PacketId]),
format_topic_filters(Topics)]; "TopicFilters=",
format_topic_filters(TopicFilters)
format_variable(#mqtt_packet_suback{packet_id = PacketId, ];
reason_codes = ReasonCodes}, _) -> format_variable(
#mqtt_packet_unsubscribe{
packet_id = PacketId,
topic_filters = Topics
},
_
) ->
[
io_lib:format("PacketId=~p ", [PacketId]),
"TopicFilters=",
format_topic_filters(Topics)
];
format_variable(
#mqtt_packet_suback{
packet_id = PacketId,
reason_codes = ReasonCodes
},
_
) ->
io_lib:format("PacketId=~p, ReasonCodes=~p", [PacketId, ReasonCodes]); io_lib:format("PacketId=~p, ReasonCodes=~p", [PacketId, ReasonCodes]);
format_variable(#mqtt_packet_unsuback{packet_id = PacketId}, _) -> format_variable(#mqtt_packet_unsuback{packet_id = PacketId}, _) ->
io_lib:format("PacketId=~p", [PacketId]); io_lib:format("PacketId=~p", [PacketId]);
format_variable(#mqtt_packet_auth{reason_code = ReasonCode}, _) -> format_variable(#mqtt_packet_auth{reason_code = ReasonCode}, _) ->
io_lib:format("ReasonCode=~p", [ReasonCode]); io_lib:format("ReasonCode=~p", [ReasonCode]);
format_variable(PacketId, _) when is_integer(PacketId) -> format_variable(PacketId, _) when is_integer(PacketId) ->
io_lib:format("PacketId=~p", [PacketId]). io_lib:format("PacketId=~p", [PacketId]).
@ -534,80 +606,92 @@ format_payload(Payload, text) -> ["Payload=", io_lib:format("~ts", [Payload])];
format_payload(Payload, hex) -> ["Payload(hex)=", encode_hex(Payload)]; format_payload(Payload, hex) -> ["Payload(hex)=", encode_hex(Payload)];
format_payload(_, hidden) -> "Payload=******". format_payload(_, hidden) -> "Payload=******".
i(true) -> 1; i(true) -> 1;
i(false) -> 0; i(false) -> 0;
i(I) when is_integer(I) -> I. i(I) when is_integer(I) -> I.
format_topic_filters(Filters) -> format_topic_filters(Filters) ->
["[", [
lists:join(",", "[",
lists:join(
",",
lists:map( lists:map(
fun({TopicFilter, SubOpts}) -> fun
io_lib:format("~ts(~p)", [TopicFilter, SubOpts]); ({TopicFilter, SubOpts}) ->
io_lib:format("~ts(~p)", [TopicFilter, SubOpts]);
(TopicFilter) -> (TopicFilter) ->
io_lib:format("~ts", [TopicFilter]) io_lib:format("~ts", [TopicFilter])
end, Filters)), end,
"]"]. Filters
)
),
"]"
].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Hex encoding functions %% Hex encoding functions
%% Copy from binary:encode_hex/1 (was only introduced in OTP24). %% Copy from binary:encode_hex/1 (was only introduced in OTP24).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(HEX(X), (hex(X)):16). -define(HEX(X), (hex(X)):16).
-compile({inline,[hex/1]}). -compile({inline, [hex/1]}).
-spec encode_hex(Bin) -> Bin2 when -spec encode_hex(Bin) -> Bin2 when
Bin :: binary(), Bin :: binary(),
Bin2 :: <<_:_*16>>. Bin2 :: <<_:_*16>>.
encode_hex(Data) when byte_size(Data) rem 8 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 8 =:= 0 ->
<< <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E),?HEX(F),?HEX(G),?HEX(H)>> || <<A,B,C,D,E,F,G,H>> <= Data >>; <<
<<?HEX(A), ?HEX(B), ?HEX(C), ?HEX(D), ?HEX(E), ?HEX(F), ?HEX(G), ?HEX(H)>>
|| <<A, B, C, D, E, F, G, H>> <= Data
>>;
encode_hex(Data) when byte_size(Data) rem 7 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 7 =:= 0 ->
<< <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E),?HEX(F),?HEX(G)>> || <<A,B,C,D,E,F,G>> <= Data >>; <<
<<?HEX(A), ?HEX(B), ?HEX(C), ?HEX(D), ?HEX(E), ?HEX(F), ?HEX(G)>>
|| <<A, B, C, D, E, F, G>> <= Data
>>;
encode_hex(Data) when byte_size(Data) rem 6 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 6 =:= 0 ->
<< <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E),?HEX(F)>> || <<A,B,C,D,E,F>> <= Data >>; <<<<?HEX(A), ?HEX(B), ?HEX(C), ?HEX(D), ?HEX(E), ?HEX(F)>> || <<A, B, C, D, E, F>> <= Data>>;
encode_hex(Data) when byte_size(Data) rem 5 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 5 =:= 0 ->
<< <<?HEX(A),?HEX(B),?HEX(C),?HEX(D),?HEX(E)>> || <<A,B,C,D,E>> <= Data >>; <<<<?HEX(A), ?HEX(B), ?HEX(C), ?HEX(D), ?HEX(E)>> || <<A, B, C, D, E>> <= Data>>;
encode_hex(Data) when byte_size(Data) rem 4 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 4 =:= 0 ->
<< <<?HEX(A),?HEX(B),?HEX(C),?HEX(D)>> || <<A,B,C,D>> <= Data >>; <<<<?HEX(A), ?HEX(B), ?HEX(C), ?HEX(D)>> || <<A, B, C, D>> <= Data>>;
encode_hex(Data) when byte_size(Data) rem 3 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 3 =:= 0 ->
<< <<?HEX(A),?HEX(B),?HEX(C)>> || <<A,B,C>> <= Data >>; <<<<?HEX(A), ?HEX(B), ?HEX(C)>> || <<A, B, C>> <= Data>>;
encode_hex(Data) when byte_size(Data) rem 2 =:= 0 -> encode_hex(Data) when byte_size(Data) rem 2 =:= 0 ->
<< <<?HEX(A),?HEX(B)>> || <<A,B>> <= Data >>; <<<<?HEX(A), ?HEX(B)>> || <<A, B>> <= Data>>;
encode_hex(Data) when is_binary(Data) -> encode_hex(Data) when is_binary(Data) ->
<< <<?HEX(N)>> || <<N>> <= Data >>; <<<<?HEX(N)>> || <<N>> <= Data>>;
encode_hex(Bin) -> encode_hex(Bin) ->
erlang:error(badarg, [Bin]). erlang:error(badarg, [Bin]).
hex(X) -> hex(X) ->
element( element(
X+1, {16#3030, 16#3031, 16#3032, 16#3033, 16#3034, 16#3035, 16#3036, 16#3037, 16#3038, 16#3039, 16#3041, X + 1,
16#3042, 16#3043, 16#3044, 16#3045, 16#3046, {16#3030, 16#3031, 16#3032, 16#3033, 16#3034, 16#3035, 16#3036, 16#3037, 16#3038, 16#3039,
16#3130, 16#3131, 16#3132, 16#3133, 16#3134, 16#3135, 16#3136, 16#3137, 16#3138, 16#3139, 16#3141, 16#3041, 16#3042, 16#3043, 16#3044, 16#3045, 16#3046, 16#3130, 16#3131, 16#3132,
16#3142, 16#3143, 16#3144, 16#3145, 16#3146, 16#3133, 16#3134, 16#3135, 16#3136, 16#3137, 16#3138, 16#3139, 16#3141, 16#3142,
16#3230, 16#3231, 16#3232, 16#3233, 16#3234, 16#3235, 16#3236, 16#3237, 16#3238, 16#3239, 16#3241, 16#3143, 16#3144, 16#3145, 16#3146, 16#3230, 16#3231, 16#3232, 16#3233, 16#3234,
16#3242, 16#3243, 16#3244, 16#3245, 16#3246, 16#3235, 16#3236, 16#3237, 16#3238, 16#3239, 16#3241, 16#3242, 16#3243, 16#3244,
16#3330, 16#3331, 16#3332, 16#3333, 16#3334, 16#3335, 16#3336, 16#3337, 16#3338, 16#3339, 16#3341, 16#3245, 16#3246, 16#3330, 16#3331, 16#3332, 16#3333, 16#3334, 16#3335, 16#3336,
16#3342, 16#3343, 16#3344, 16#3345, 16#3346, 16#3337, 16#3338, 16#3339, 16#3341, 16#3342, 16#3343, 16#3344, 16#3345, 16#3346,
16#3430, 16#3431, 16#3432, 16#3433, 16#3434, 16#3435, 16#3436, 16#3437, 16#3438, 16#3439, 16#3441, 16#3430, 16#3431, 16#3432, 16#3433, 16#3434, 16#3435, 16#3436, 16#3437, 16#3438,
16#3442, 16#3443, 16#3444, 16#3445, 16#3446, 16#3439, 16#3441, 16#3442, 16#3443, 16#3444, 16#3445, 16#3446, 16#3530, 16#3531,
16#3530, 16#3531, 16#3532, 16#3533, 16#3534, 16#3535, 16#3536, 16#3537, 16#3538, 16#3539, 16#3541, 16#3532, 16#3533, 16#3534, 16#3535, 16#3536, 16#3537, 16#3538, 16#3539, 16#3541,
16#3542, 16#3543, 16#3544, 16#3545, 16#3546, 16#3542, 16#3543, 16#3544, 16#3545, 16#3546, 16#3630, 16#3631, 16#3632, 16#3633,
16#3630, 16#3631, 16#3632, 16#3633, 16#3634, 16#3635, 16#3636, 16#3637, 16#3638, 16#3639, 16#3641, 16#3634, 16#3635, 16#3636, 16#3637, 16#3638, 16#3639, 16#3641, 16#3642, 16#3643,
16#3642, 16#3643, 16#3644, 16#3645, 16#3646, 16#3644, 16#3645, 16#3646, 16#3730, 16#3731, 16#3732, 16#3733, 16#3734, 16#3735,
16#3730, 16#3731, 16#3732, 16#3733, 16#3734, 16#3735, 16#3736, 16#3737, 16#3738, 16#3739, 16#3741, 16#3736, 16#3737, 16#3738, 16#3739, 16#3741, 16#3742, 16#3743, 16#3744, 16#3745,
16#3742, 16#3743, 16#3744, 16#3745, 16#3746, 16#3746, 16#3830, 16#3831, 16#3832, 16#3833, 16#3834, 16#3835, 16#3836, 16#3837,
16#3830, 16#3831, 16#3832, 16#3833, 16#3834, 16#3835, 16#3836, 16#3837, 16#3838, 16#3839, 16#3841, 16#3838, 16#3839, 16#3841, 16#3842, 16#3843, 16#3844, 16#3845, 16#3846, 16#3930,
16#3842, 16#3843, 16#3844, 16#3845, 16#3846, 16#3931, 16#3932, 16#3933, 16#3934, 16#3935, 16#3936, 16#3937, 16#3938, 16#3939,
16#3930, 16#3931, 16#3932, 16#3933, 16#3934, 16#3935, 16#3936, 16#3937, 16#3938, 16#3939, 16#3941, 16#3941, 16#3942, 16#3943, 16#3944, 16#3945, 16#3946, 16#4130, 16#4131, 16#4132,
16#3942, 16#3943, 16#3944, 16#3945, 16#3946, 16#4133, 16#4134, 16#4135, 16#4136, 16#4137, 16#4138, 16#4139, 16#4141, 16#4142,
16#4130, 16#4131, 16#4132, 16#4133, 16#4134, 16#4135, 16#4136, 16#4137, 16#4138, 16#4139, 16#4141, 16#4143, 16#4144, 16#4145, 16#4146, 16#4230, 16#4231, 16#4232, 16#4233, 16#4234,
16#4142, 16#4143, 16#4144, 16#4145, 16#4146, 16#4235, 16#4236, 16#4237, 16#4238, 16#4239, 16#4241, 16#4242, 16#4243, 16#4244,
16#4230, 16#4231, 16#4232, 16#4233, 16#4234, 16#4235, 16#4236, 16#4237, 16#4238, 16#4239, 16#4241, 16#4245, 16#4246, 16#4330, 16#4331, 16#4332, 16#4333, 16#4334, 16#4335, 16#4336,
16#4242, 16#4243, 16#4244, 16#4245, 16#4246, 16#4337, 16#4338, 16#4339, 16#4341, 16#4342, 16#4343, 16#4344, 16#4345, 16#4346,
16#4330, 16#4331, 16#4332, 16#4333, 16#4334, 16#4335, 16#4336, 16#4337, 16#4338, 16#4339, 16#4341, 16#4430, 16#4431, 16#4432, 16#4433, 16#4434, 16#4435, 16#4436, 16#4437, 16#4438,
16#4342, 16#4343, 16#4344, 16#4345, 16#4346, 16#4439, 16#4441, 16#4442, 16#4443, 16#4444, 16#4445, 16#4446, 16#4530, 16#4531,
16#4430, 16#4431, 16#4432, 16#4433, 16#4434, 16#4435, 16#4436, 16#4437, 16#4438, 16#4439, 16#4441, 16#4532, 16#4533, 16#4534, 16#4535, 16#4536, 16#4537, 16#4538, 16#4539, 16#4541,
16#4442, 16#4443, 16#4444, 16#4445, 16#4446, 16#4542, 16#4543, 16#4544, 16#4545, 16#4546, 16#4630, 16#4631, 16#4632, 16#4633,
16#4530, 16#4531, 16#4532, 16#4533, 16#4534, 16#4535, 16#4536, 16#4537, 16#4538, 16#4539, 16#4541, 16#4634, 16#4635, 16#4636, 16#4637, 16#4638, 16#4639, 16#4641, 16#4642, 16#4643,
16#4542, 16#4543, 16#4544, 16#4545, 16#4546, 16#4644, 16#4645, 16#4646}
16#4630, 16#4631, 16#4632, 16#4633, 16#4634, 16#4635, 16#4636, 16#4637, 16#4638, 16#4639, 16#4641, ).
16#4642, 16#4643, 16#4644, 16#4645, 16#4646}).

View File

@ -16,37 +16,40 @@
-module(emqx_passwd). -module(emqx_passwd).
-export([ hash/2 -export([
, hash_data/2 hash/2,
, check_pass/3 hash_data/2,
]). check_pass/3
]).
-export_type([ password/0 -export_type([
, password_hash/0 password/0,
, hash_type_simple/0 password_hash/0,
, hash_type/0 hash_type_simple/0,
, salt_position/0 hash_type/0,
, salt/0]). salt_position/0,
salt/0
]).
-include("logger.hrl"). -include("logger.hrl").
-type(password() :: binary()). -type password() :: binary().
-type(password_hash() :: binary()). -type password_hash() :: binary().
-type(hash_type_simple() :: plain | md5 | sha | sha256 | sha512). -type hash_type_simple() :: plain | md5 | sha | sha256 | sha512.
-type(hash_type() :: hash_type_simple() | bcrypt | pbkdf2). -type hash_type() :: hash_type_simple() | bcrypt | pbkdf2.
-type(salt_position() :: prefix | suffix). -type salt_position() :: prefix | suffix.
-type(salt() :: binary()). -type salt() :: binary().
-type(pbkdf2_mac_fun() :: md4 | md5 | ripemd160 | sha | sha224 | sha256 | sha384 | sha512). -type pbkdf2_mac_fun() :: md4 | md5 | ripemd160 | sha | sha224 | sha256 | sha384 | sha512.
-type(pbkdf2_iterations() :: pos_integer()). -type pbkdf2_iterations() :: pos_integer().
-type(pbkdf2_dk_length() :: pos_integer() | undefined). -type pbkdf2_dk_length() :: pos_integer() | undefined.
-type(hash_params() :: -type hash_params() ::
{bcrypt, salt()} | {bcrypt, salt()}
{pbkdf2, pbkdf2_mac_fun(), salt(), pbkdf2_iterations(), pbkdf2_dk_length()} | | {pbkdf2, pbkdf2_mac_fun(), salt(), pbkdf2_iterations(), pbkdf2_dk_length()}
{hash_type_simple(), salt(), salt_position()}). | {hash_type_simple(), salt(), salt_position()}.
-export_type([pbkdf2_mac_fun/0]). -export_type([pbkdf2_mac_fun/0]).
@ -54,38 +57,38 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(check_pass(hash_params(), password_hash(), password()) -> boolean()). -spec check_pass(hash_params(), password_hash(), password()) -> boolean().
check_pass({pbkdf2, MacFun, Salt, Iterations, DKLength}, PasswordHash, Password) -> check_pass({pbkdf2, MacFun, Salt, Iterations, DKLength}, PasswordHash, Password) ->
case pbkdf2(MacFun, Password, Salt, Iterations, DKLength) of case pbkdf2(MacFun, Password, Salt, Iterations, DKLength) of
{ok, HashPasswd} -> {ok, HashPasswd} ->
compare_secure(hex(HashPasswd), PasswordHash); compare_secure(hex(HashPasswd), PasswordHash);
{error, _Reason}-> {error, _Reason} ->
false false
end; end;
check_pass({bcrypt, Salt}, PasswordHash, Password) -> check_pass({bcrypt, Salt}, PasswordHash, Password) ->
case bcrypt:hashpw(Password, Salt) of case bcrypt:hashpw(Password, Salt) of
{ok, HashPasswd} -> {ok, HashPasswd} ->
compare_secure(list_to_binary(HashPasswd), PasswordHash); compare_secure(list_to_binary(HashPasswd), PasswordHash);
{error, _Reason}-> {error, _Reason} ->
false false
end; end;
check_pass({_SimpleHash, _Salt, _SaltPosition} = HashParams, PasswordHash, Password) -> check_pass({_SimpleHash, _Salt, _SaltPosition} = HashParams, PasswordHash, Password) ->
Hash = hash(HashParams, Password), Hash = hash(HashParams, Password),
compare_secure(Hash, PasswordHash). compare_secure(Hash, PasswordHash).
-spec(hash(hash_params(), password()) -> password_hash()). -spec hash(hash_params(), password()) -> password_hash().
hash({pbkdf2, MacFun, Salt, Iterations, DKLength}, Password) -> hash({pbkdf2, MacFun, Salt, Iterations, DKLength}, Password) ->
case pbkdf2(MacFun, Password, Salt, Iterations, DKLength) of case pbkdf2(MacFun, Password, Salt, Iterations, DKLength) of
{ok, HashPasswd} -> {ok, HashPasswd} ->
hex(HashPasswd); hex(HashPasswd);
{error, Reason}-> {error, Reason} ->
error(Reason) error(Reason)
end; end;
hash({bcrypt, Salt}, Password) -> hash({bcrypt, Salt}, Password) ->
case bcrypt:hashpw(Password, Salt) of case bcrypt:hashpw(Password, Salt) of
{ok, HashPasswd} -> {ok, HashPasswd} ->
list_to_binary(HashPasswd); list_to_binary(HashPasswd);
{error, Reason}-> {error, Reason} ->
error(Reason) error(Reason)
end; end;
hash({SimpleHash, Salt, prefix}, Password) when is_binary(Password), is_binary(Salt) -> hash({SimpleHash, Salt, prefix}, Password) when is_binary(Password), is_binary(Salt) ->
@ -93,8 +96,7 @@ hash({SimpleHash, Salt, prefix}, Password) when is_binary(Password), is_binary(S
hash({SimpleHash, Salt, suffix}, Password) when is_binary(Password), is_binary(Salt) -> hash({SimpleHash, Salt, suffix}, Password) when is_binary(Password), is_binary(Salt) ->
hash_data(SimpleHash, <<Password/binary, Salt/binary>>). hash_data(SimpleHash, <<Password/binary, Salt/binary>>).
-spec hash_data(hash_type(), binary()) -> binary().
-spec(hash_data(hash_type(), binary()) -> binary()).
hash_data(plain, Data) when is_binary(Data) -> hash_data(plain, Data) when is_binary(Data) ->
Data; Data;
hash_data(md5, Data) when is_binary(Data) -> hash_data(md5, Data) when is_binary(Data) ->
@ -111,26 +113,24 @@ hash_data(sha512, Data) when is_binary(Data) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
compare_secure(X, Y) when is_binary(X), is_binary(Y) -> compare_secure(X, Y) when is_binary(X), is_binary(Y) ->
compare_secure(binary_to_list(X), binary_to_list(Y)); compare_secure(binary_to_list(X), binary_to_list(Y));
compare_secure(X, Y) when is_list(X), is_list(Y) -> compare_secure(X, Y) when is_list(X), is_list(Y) ->
case length(X) == length(Y) of case length(X) == length(Y) of
true -> true ->
compare_secure(X, Y, 0); compare_secure(X, Y, 0);
false -> false ->
false false
end. end.
compare_secure([X | RestX], [Y | RestY], Result) -> compare_secure([X | RestX], [Y | RestY], Result) ->
compare_secure(RestX, RestY, (X bxor Y) bor Result); compare_secure(RestX, RestY, (X bxor Y) bor Result);
compare_secure([], [], Result) -> compare_secure([], [], Result) ->
Result == 0. Result == 0.
pbkdf2(MacFun, Password, Salt, Iterations, undefined) -> pbkdf2(MacFun, Password, Salt, Iterations, undefined) ->
pbkdf2:pbkdf2(MacFun, Password, Salt, Iterations); pbkdf2:pbkdf2(MacFun, Password, Salt, Iterations);
pbkdf2(MacFun, Password, Salt, Iterations, DKLength) -> pbkdf2(MacFun, Password, Salt, Iterations, DKLength) ->
pbkdf2:pbkdf2(MacFun, Password, Salt, Iterations, DKLength). pbkdf2:pbkdf2(MacFun, Password, Salt, Iterations, DKLength).
hex(X) when is_binary(X) -> hex(X) when is_binary(X) ->
pbkdf2:to_hex(X). pbkdf2:to_hex(X).

View File

@ -19,34 +19,42 @@
-include("types.hrl"). -include("types.hrl").
-export([ get_counters/1 -export([
, get_counter/1 get_counters/1,
, inc_counter/2 get_counter/1,
, reset_counter/1 inc_counter/2,
]). reset_counter/1
]).
-compile({inline, -compile(
[ get_counters/1 {inline, [
, get_counter/1 get_counters/1,
, inc_counter/2 get_counter/1,
, reset_counter/1 inc_counter/2,
]}). reset_counter/1
]}
).
-type(key() :: term()). -type key() :: term().
-spec(get_counters(list(key())) -> list({key(), number()})). -spec get_counters(list(key())) -> list({key(), number()}).
get_counters(Keys) when is_list(Keys) -> get_counters(Keys) when is_list(Keys) ->
[{Key, emqx_pd:get_counter(Key)} || Key <- Keys]. [{Key, emqx_pd:get_counter(Key)} || Key <- Keys].
-spec(get_counter(key()) -> number()). -spec get_counter(key()) -> number().
get_counter(Key) -> get_counter(Key) ->
case get(Key) of undefined -> 0; Cnt -> Cnt end. case get(Key) of
undefined -> 0;
Cnt -> Cnt
end.
-spec(inc_counter(key(), number()) -> maybe(number())). -spec inc_counter(key(), number()) -> maybe(number()).
inc_counter(Key, Inc) -> inc_counter(Key, Inc) ->
put(Key, get_counter(Key) + Inc). put(Key, get_counter(Key) + Inc).
-spec(reset_counter(key()) -> number()). -spec reset_counter(key()) -> number().
reset_counter(Key) -> reset_counter(Key) ->
case put(Key, 0) of undefined -> 0; Cnt -> Cnt end. case put(Key, 0) of
undefined -> 0;
Cnt -> Cnt
end.

View File

@ -16,42 +16,47 @@
-module(emqx_persistent_session). -module(emqx_persistent_session).
-export([ is_store_enabled/0 -export([
, init_db_backend/0 is_store_enabled/0,
, storage_type/0 init_db_backend/0,
]). storage_type/0
]).
-export([ discard/2 -export([
, discard_if_present/1 discard/2,
, lookup/1 discard_if_present/1,
, persist/3 lookup/1,
, persist_message/1 persist/3,
, pending/1 persist_message/1,
, pending/2 pending/1,
, resume/3 pending/2,
]). resume/3
]).
-export([ add_subscription/3 -export([
, remove_subscription/3 add_subscription/3,
]). remove_subscription/3
]).
-export([ mark_as_delivered/2 -export([
, mark_resume_begin/1 mark_as_delivered/2,
]). mark_resume_begin/1
]).
-export([ pending_messages_in_db/2 -export([
, delete_session_message/1 pending_messages_in_db/2,
, gc_session_messages/1 delete_session_message/1,
, session_message_info/2 gc_session_messages/1,
]). session_message_info/2
]).
-export([ delete_message/1 -export([
, first_message_id/0 delete_message/1,
, next_message_id/1 first_message_id/0,
]). next_message_id/1
]).
-export_type([ sess_msg_key/0 -export_type([sess_msg_key/0]).
]).
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_persistent_session.hrl"). -include("emqx_persistent_session.hrl").
@ -59,7 +64,8 @@
-compile({inline, [is_store_enabled/0]}). -compile({inline, [is_store_enabled/0]}).
-define(MAX_EXPIRY_INTERVAL, 4294967295000). %% 16#FFFFFFFF * 1000 %% 16#FFFFFFFF * 1000
-define(MAX_EXPIRY_INTERVAL, 4294967295000).
%% NOTE: Order is significant because of traversal order of the table. %% NOTE: Order is significant because of traversal order of the table.
-define(MARKER, 3). -define(MARKER, 3).
@ -67,12 +73,11 @@
-define(UNDELIVERED, 1). -define(UNDELIVERED, 1).
-define(ABANDONED, 0). -define(ABANDONED, 0).
-type bin_timestamp() :: <<_:64>>. -type bin_timestamp() :: <<_:64>>.
-opaque sess_msg_key() :: -opaque sess_msg_key() ::
{emqx_guid:guid(), emqx_guid:guid(), emqx_types:topic(), ?UNDELIVERED | ?DELIVERED} {emqx_guid:guid(), emqx_guid:guid(), emqx_types:topic(), ?UNDELIVERED | ?DELIVERED}
| {emqx_guid:guid(), emqx_guid:guid(), <<>> , ?MARKER} | {emqx_guid:guid(), emqx_guid:guid(), <<>>, ?MARKER}
| {emqx_guid:guid(), <<>> , bin_timestamp() , ?ABANDONED}. | {emqx_guid:guid(), <<>>, bin_timestamp(), ?ABANDONED}.
-type gc_traverse_fun() :: fun(('delete' | 'marker' | 'abandoned', sess_msg_key()) -> 'ok'). -type gc_traverse_fun() :: fun(('delete' | 'marker' | 'abandoned', sess_msg_key()) -> 'ok').
@ -82,14 +87,16 @@
init_db_backend() -> init_db_backend() ->
case is_store_enabled() of case is_store_enabled() of
true -> true ->
StorageType = storage_type(), StorageType = storage_type(),
ok = emqx_trie:create_session_trie(StorageType), ok = emqx_trie:create_session_trie(StorageType),
ok = emqx_session_router:create_router_tab(StorageType), ok = emqx_session_router:create_router_tab(StorageType),
case StorageType of case StorageType of
disc -> disc ->
emqx_persistent_session_mnesia_disc_backend:create_tables(), emqx_persistent_session_mnesia_disc_backend:create_tables(),
persistent_term:put(?db_backend_key, emqx_persistent_session_mnesia_disc_backend); persistent_term:put(
?db_backend_key, emqx_persistent_session_mnesia_disc_backend
);
ram -> ram ->
emqx_persistent_session_mnesia_ram_backend:create_tables(), emqx_persistent_session_mnesia_ram_backend:create_tables(),
persistent_term:put(?db_backend_key, emqx_persistent_session_mnesia_ram_backend) persistent_term:put(?db_backend_key, emqx_persistent_session_mnesia_ram_backend)
@ -112,7 +119,7 @@ storage_type() ->
-spec session_message_info('timestamp' | 'sessionID', sess_msg_key()) -> term(). -spec session_message_info('timestamp' | 'sessionID', sess_msg_key()) -> term().
session_message_info(timestamp, {_, <<>>, <<TS:64>>, ?ABANDONED}) -> TS; session_message_info(timestamp, {_, <<>>, <<TS:64>>, ?ABANDONED}) -> TS;
session_message_info(timestamp, {_, GUID, _ , _ }) -> emqx_guid:timestamp(GUID); session_message_info(timestamp, {_, GUID, _, _}) -> emqx_guid:timestamp(GUID);
session_message_info(sessionID, {SessionID, _, _, _}) -> SessionID. session_message_info(sessionID, {SessionID, _, _, _}) -> SessionID.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -147,7 +154,7 @@ lookup_session_store(ClientID) ->
?db_backend:lookup_session_store(ClientID). ?db_backend:lookup_session_store(ClientID).
put_session_message({_, _, _, _} = Key) -> put_session_message({_, _, _, _} = Key) ->
?db_backend:put_session_message(#session_msg{ key = Key }). ?db_backend:put_session_message(#session_msg{key = Key}).
put_message(Msg) -> put_message(Msg) ->
?db_backend:put_message(Msg). ?db_backend:put_message(Msg).
@ -164,29 +171,37 @@ pending_messages_in_db(SessionID, MarkerIds) ->
%% The timestamp (TS) is the last time a client interacted with the session, %% The timestamp (TS) is the last time a client interacted with the session,
%% or when the client disconnected. %% or when the client disconnected.
-spec persist(emqx_types:clientinfo(), -spec persist(
emqx_types:conninfo(), emqx_types:clientinfo(),
emqx_session:session()) -> emqx_session:session(). emqx_types:conninfo(),
emqx_session:session()
) -> emqx_session:session().
persist(#{ clientid := ClientID }, ConnInfo, Session) -> persist(#{clientid := ClientID}, ConnInfo, Session) ->
case ClientID == undefined orelse not emqx_session:info(is_persistent, Session) of case ClientID == undefined orelse not emqx_session:info(is_persistent, Session) of
true -> Session; true ->
Session;
false -> false ->
SS = #session_store{ client_id = ClientID SS = #session_store{
, expiry_interval = maps:get(expiry_interval, ConnInfo) client_id = ClientID,
, ts = timestamp_from_conninfo(ConnInfo) expiry_interval = maps:get(expiry_interval, ConnInfo),
, session = Session}, ts = timestamp_from_conninfo(ConnInfo),
session = Session
},
case persistent_session_status(SS) of case persistent_session_status(SS) of
not_persistent -> Session; not_persistent ->
expired -> discard(ClientID, Session); Session;
persistent -> put_session_store(SS), expired ->
Session discard(ClientID, Session);
persistent ->
put_session_store(SS),
Session
end end
end. end.
timestamp_from_conninfo(ConnInfo) -> timestamp_from_conninfo(ConnInfo) ->
case maps:get(disconnected_at, ConnInfo, undefined) of case maps:get(disconnected_at, ConnInfo, undefined) of
undefined -> erlang:system_time(millisecond); undefined -> erlang:system_time(millisecond);
Disconnect -> Disconnect Disconnect -> Disconnect
end. end.
@ -196,11 +211,12 @@ lookup(ClientID) when is_binary(ClientID) ->
none; none;
true -> true ->
case lookup_session_store(ClientID) of case lookup_session_store(ClientID) of
none -> none; none ->
none;
{value, #session_store{session = S} = SS} -> {value, #session_store{session = S} = SS} ->
case persistent_session_status(SS) of case persistent_session_status(SS) of
expired -> {expired, S}; expired -> {expired, S};
persistent -> {persistent, S} persistent -> {persistent, S}
end end
end end
end. end.
@ -208,7 +224,8 @@ lookup(ClientID) when is_binary(ClientID) ->
-spec discard_if_present(binary()) -> 'ok'. -spec discard_if_present(binary()) -> 'ok'.
discard_if_present(ClientID) -> discard_if_present(ClientID) ->
case lookup(ClientID) of case lookup(ClientID) of
none -> ok; none ->
ok;
{Tag, Session} when Tag =:= persistent; Tag =:= expired -> {Tag, Session} when Tag =:= persistent; Tag =:= expired ->
_ = discard(ClientID, Session), _ = discard(ClientID, Session),
ok ok
@ -218,12 +235,12 @@ discard_if_present(ClientID) ->
discard(ClientID, Session) -> discard(ClientID, Session) ->
discard_opt(is_store_enabled(), ClientID, Session). discard_opt(is_store_enabled(), ClientID, Session).
discard_opt(false,_ClientID, Session) -> discard_opt(false, _ClientID, Session) ->
emqx_session:set_field(is_persistent, false, Session); emqx_session:set_field(is_persistent, false, Session);
discard_opt(true, ClientID, Session) -> discard_opt(true, ClientID, Session) ->
delete_session_store(ClientID), delete_session_store(ClientID),
SessionID = emqx_session:info(id, Session), SessionID = emqx_session:info(id, Session),
put_session_message({SessionID, <<>>, << (erlang:system_time(microsecond)) : 64>>, ?ABANDONED}), put_session_message({SessionID, <<>>, <<(erlang:system_time(microsecond)):64>>, ?ABANDONED}),
Subscriptions = emqx_session:info(subscriptions, Session), Subscriptions = emqx_session:info(subscriptions, Session),
emqx_session_router:delete_routes(SessionID, Subscriptions), emqx_session_router:delete_routes(SessionID, Subscriptions),
emqx_session:set_field(is_persistent, false, Session). emqx_session:set_field(is_persistent, false, Session).
@ -236,7 +253,7 @@ mark_resume_begin(SessionID) ->
add_subscription(TopicFilter, SessionID, true = _IsPersistent) -> add_subscription(TopicFilter, SessionID, true = _IsPersistent) ->
case is_store_enabled() of case is_store_enabled() of
true -> emqx_session_router:do_add_route(TopicFilter, SessionID); true -> emqx_session_router:do_add_route(TopicFilter, SessionID);
false -> ok false -> ok
end; end;
add_subscription(_TopicFilter, _SessionID, false = _IsPersistent) -> add_subscription(_TopicFilter, _SessionID, false = _IsPersistent) ->
@ -244,7 +261,7 @@ add_subscription(_TopicFilter, _SessionID, false = _IsPersistent) ->
remove_subscription(TopicFilter, SessionID, true = _IsPersistent) -> remove_subscription(TopicFilter, SessionID, true = _IsPersistent) ->
case is_store_enabled() of case is_store_enabled() of
true -> emqx_session_router:do_delete_route(TopicFilter, SessionID); true -> emqx_session_router:do_delete_route(TopicFilter, SessionID);
false -> ok false -> ok
end; end;
remove_subscription(_TopicFilter, _SessionID, false = _IsPersistent) -> remove_subscription(_TopicFilter, _SessionID, false = _IsPersistent) ->
@ -255,8 +272,8 @@ remove_subscription(_TopicFilter, _SessionID, false = _IsPersistent) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Must be called inside a emqx_cm_locker transaction. %% Must be called inside a emqx_cm_locker transaction.
-spec resume(emqx_types:clientinfo(), emqx_types:conninfo(), emqx_session:session() -spec resume(emqx_types:clientinfo(), emqx_types:conninfo(), emqx_session:session()) ->
) -> {emqx_session:session(), [emqx_types:deliver()]}. {emqx_session:session(), [emqx_types:deliver()]}.
resume(ClientInfo = #{clientid := ClientID}, ConnInfo, Session) -> resume(ClientInfo = #{clientid := ClientID}, ConnInfo, Session) ->
SessionID = emqx_session:info(id, Session), SessionID = emqx_session:info(id, Session),
?tp(ps_resuming, #{from => db, sid => SessionID}), ?tp(ps_resuming, #{from => db, sid => SessionID}),
@ -267,8 +284,10 @@ resume(ClientInfo = #{clientid := ClientID}, ConnInfo, Session) ->
?tp(ps_initial_pendings, #{sid => SessionID}), ?tp(ps_initial_pendings, #{sid => SessionID}),
Pendings1 = pending(SessionID), Pendings1 = pending(SessionID),
Pendings2 = emqx_session:ignore_local(ClientInfo, Pendings1, ClientID, Session), Pendings2 = emqx_session:ignore_local(ClientInfo, Pendings1, ClientID, Session),
?tp(ps_got_initial_pendings, #{ sid => SessionID ?tp(ps_got_initial_pendings, #{
, msgs => Pendings1}), sid => SessionID,
msgs => Pendings1
}),
%% 2. Enqueue messages to mimic that the process was alive %% 2. Enqueue messages to mimic that the process was alive
%% when the messages were delivered. %% when the messages were delivered.
@ -276,8 +295,10 @@ resume(ClientInfo = #{clientid := ClientID}, ConnInfo, Session) ->
Session1 = emqx_session:enqueue(ClientInfo, Pendings2, Session), Session1 = emqx_session:enqueue(ClientInfo, Pendings2, Session),
Session2 = persist(ClientInfo, ConnInfo, Session1), Session2 = persist(ClientInfo, ConnInfo, Session1),
mark_as_delivered(SessionID, Pendings2), mark_as_delivered(SessionID, Pendings2),
?tp(ps_persist_pendings_msgs, #{ msgs => Pendings2 ?tp(ps_persist_pendings_msgs, #{
, sid => SessionID}), msgs => Pendings2,
sid => SessionID
}),
%% 3. Notify writers that we are resuming. %% 3. Notify writers that we are resuming.
%% They will buffer new messages. %% They will buffer new messages.
@ -295,14 +316,18 @@ resume(ClientInfo = #{clientid := ClientID}, ConnInfo, Session) ->
MarkerIDs = [Marker || {_, Marker} <- NodeMarkers], MarkerIDs = [Marker || {_, Marker} <- NodeMarkers],
Pendings3 = pending(SessionID, MarkerIDs), Pendings3 = pending(SessionID, MarkerIDs),
Pendings4 = emqx_session:ignore_local(ClientInfo, Pendings3, ClientID, Session), Pendings4 = emqx_session:ignore_local(ClientInfo, Pendings3, ClientID, Session),
?tp(ps_marker_pendings_msgs, #{ sid => SessionID ?tp(ps_marker_pendings_msgs, #{
, msgs => Pendings4}), sid => SessionID,
msgs => Pendings4
}),
%% 6. Get pending messages from writers. %% 6. Get pending messages from writers.
?tp(ps_resume_end, #{sid => SessionID}), ?tp(ps_resume_end, #{sid => SessionID}),
WriterPendings = resume_end(Nodes, SessionID), WriterPendings = resume_end(Nodes, SessionID),
?tp(ps_writer_pendings, #{ msgs => WriterPendings ?tp(ps_writer_pendings, #{
, sid => SessionID}), msgs => WriterPendings,
sid => SessionID
}),
%% 7. Drain the inbox and usort the messages %% 7. Drain the inbox and usort the messages
%% with the pending messages. (Should be done by caller.) %% with the pending messages. (Should be done by caller.)
@ -314,30 +339,32 @@ resume_begin(Nodes, SessionID) ->
resume_end(Nodes, SessionID) -> resume_end(Nodes, SessionID) ->
Res = emqx_persistent_session_proto_v1:resume_end(Nodes, self(), SessionID), Res = emqx_persistent_session_proto_v1:resume_end(Nodes, self(), SessionID),
?tp(ps_erpc_multical_result, #{ res => Res, sid => SessionID }), ?tp(ps_erpc_multical_result, #{res => Res, sid => SessionID}),
%% TODO: Should handle the errors %% TODO: Should handle the errors
[ {deliver, STopic, M} [
|| {ok, {ok, Messages}} <- Res, {deliver, STopic, M}
{{M, STopic}} <- Messages || {ok, {ok, Messages}} <- Res,
{{M, STopic}} <- Messages
]. ].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Messages API %% Messages API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
persist_message(Msg) -> persist_message(Msg) ->
case is_store_enabled() of case is_store_enabled() of
true -> do_persist_message(Msg); true -> do_persist_message(Msg);
false -> ok false -> ok
end. end.
do_persist_message(Msg) -> do_persist_message(Msg) ->
case emqx_message:get_flag(dup, Msg) orelse emqx_message:is_sys(Msg) of case emqx_message:get_flag(dup, Msg) orelse emqx_message:is_sys(Msg) of
true -> ok; true ->
ok;
false -> false ->
case emqx_session_router:match_routes(emqx_message:topic(Msg)) of case emqx_session_router:match_routes(emqx_message:topic(Msg)) of
[] -> ok; [] ->
ok;
Routes -> Routes ->
put_message(Msg), put_message(Msg),
MsgId = emqx_message:id(Msg), MsgId = emqx_message:id(Msg),
@ -345,7 +372,7 @@ do_persist_message(Msg) ->
end end
end. end.
persist_message_routes([#route{dest = SessionID, topic = STopic}|Left], MsgId, Msg) -> persist_message_routes([#route{dest = SessionID, topic = STopic} | Left], MsgId, Msg) ->
?tp(ps_persist_msg, #{sid => SessionID, payload => emqx_message:payload(Msg)}), ?tp(ps_persist_msg, #{sid => SessionID, payload => emqx_message:payload(Msg)}),
put_session_message({SessionID, MsgId, STopic, ?UNDELIVERED}), put_session_message({SessionID, MsgId, STopic, ?UNDELIVERED}),
emqx_session_router:buffer(SessionID, STopic, Msg), emqx_session_router:buffer(SessionID, STopic, Msg),
@ -355,11 +382,11 @@ persist_message_routes([], _MsgId, _Msg) ->
mark_as_delivered(SessionID, List) -> mark_as_delivered(SessionID, List) ->
case is_store_enabled() of case is_store_enabled() of
true -> do_mark_as_delivered(SessionID, List); true -> do_mark_as_delivered(SessionID, List);
false -> ok false -> ok
end. end.
do_mark_as_delivered(SessionID, [{deliver, STopic, Msg}|Left]) -> do_mark_as_delivered(SessionID, [{deliver, STopic, Msg} | Left]) ->
MsgID = emqx_message:id(Msg), MsgID = emqx_message:id(Msg),
case next_session_message({SessionID, MsgID, STopic, ?ABANDONED}) of case next_session_message({SessionID, MsgID, STopic, ?ABANDONED}) of
{SessionID, MsgID, STopic, ?UNDELIVERED} = Key -> {SessionID, MsgID, STopic, ?UNDELIVERED} = Key ->
@ -374,12 +401,12 @@ do_mark_as_delivered(_SessionID, []) ->
ok. ok.
-spec pending(emqx_session:sessionID()) -> -spec pending(emqx_session:sessionID()) ->
[{emqx_types:message(), STopic :: binary()}]. [{emqx_types:message(), STopic :: binary()}].
pending(SessionID) -> pending(SessionID) ->
pending_messages_in_db(SessionID, []). pending_messages_in_db(SessionID, []).
-spec pending(emqx_session:sessionID(), MarkerIDs :: [emqx_guid:guid()]) -> -spec pending(emqx_session:sessionID(), MarkerIDs :: [emqx_guid:guid()]) ->
[{emqx_types:message(), STopic :: binary()}]. [{emqx_types:message(), STopic :: binary()}].
pending(SessionID, MarkerIds) -> pending(SessionID, MarkerIds) ->
%% TODO: Handle lost MarkerIDs %% TODO: Handle lost MarkerIDs
case emqx_session_router:pending(SessionID, MarkerIds) of case emqx_session_router:pending(SessionID, MarkerIds) of
@ -401,7 +428,7 @@ persistent_session_status(#session_store{expiry_interval = ?MAX_EXPIRY_INTERVAL}
persistent; persistent;
persistent_session_status(#session_store{expiry_interval = E, ts = TS}) -> persistent_session_status(#session_store{expiry_interval = E, ts = TS}) ->
case E + TS > erlang:system_time(millisecond) of case E + TS > erlang:system_time(millisecond) of
true -> persistent; true -> persistent;
false -> expired false -> expired
end. end.
@ -413,20 +440,25 @@ pending_messages_fun(SessionID, MarkerIds) ->
fun() -> fun() ->
case pending_messages({SessionID, <<>>, <<>>, ?DELIVERED}, [], MarkerIds) of case pending_messages({SessionID, <<>>, <<>>, ?DELIVERED}, [], MarkerIds) of
{Pending, []} -> read_pending_msgs(Pending, []); {Pending, []} -> read_pending_msgs(Pending, []);
{_Pending, [_|_]} -> incomplete {_Pending, [_ | _]} -> incomplete
end end
end. end.
read_pending_msgs([{MsgId, STopic}|Left], Acc) -> read_pending_msgs([{MsgId, STopic} | Left], Acc) ->
Acc1 = try [{deliver, STopic, get_message(MsgId)}|Acc] Acc1 =
catch error:{msg_not_found, _} -> try
HighwaterMark = erlang:system_time(microsecond) [{deliver, STopic, get_message(MsgId)} | Acc]
- emqx_config:get(?msg_retain) * 1000, catch
case emqx_guid:timestamp(MsgId) < HighwaterMark of error:{msg_not_found, _} ->
true -> Acc; %% Probably cleaned by GC HighwaterMark =
false -> error({msg_not_found, MsgId}) erlang:system_time(microsecond) -
end emqx_config:get(?msg_retain) * 1000,
end, case emqx_guid:timestamp(MsgId) < HighwaterMark of
%% Probably cleaned by GC
true -> Acc;
false -> error({msg_not_found, MsgId})
end
end,
read_pending_msgs(Left, Acc1); read_pending_msgs(Left, Acc1);
read_pending_msgs([], Acc) -> read_pending_msgs([], Acc) ->
lists:reverse(Acc). lists:reverse(Acc).
@ -450,21 +482,24 @@ pending_messages({SessionID, PrevMsgId, PrevSTopic, PrevTag} = PrevKey, Acc, Mar
MarkerIds1 = MarkerIds -- [MsgId], MarkerIds1 = MarkerIds -- [MsgId],
case PrevTag =:= ?UNDELIVERED of case PrevTag =:= ?UNDELIVERED of
false -> pending_messages(Key, Acc, MarkerIds1); false -> pending_messages(Key, Acc, MarkerIds1);
true -> pending_messages(Key, [{PrevMsgId, PrevSTopic}|Acc], MarkerIds1) true -> pending_messages(Key, [{PrevMsgId, PrevSTopic} | Acc], MarkerIds1)
end; end;
{S, MsgId, STopic, ?DELIVERED} = Key when S =:= SessionID, {S, MsgId, STopic, ?DELIVERED} = Key when
MsgId =:= PrevMsgId, S =:= SessionID,
STopic =:= PrevSTopic -> MsgId =:= PrevMsgId,
STopic =:= PrevSTopic
->
pending_messages(Key, Acc, MarkerIds); pending_messages(Key, Acc, MarkerIds);
{S, _MsgId, _STopic, _Tag} = Key when S =:= SessionID -> {S, _MsgId, _STopic, _Tag} = Key when S =:= SessionID ->
case PrevTag =:= ?UNDELIVERED of case PrevTag =:= ?UNDELIVERED of
false -> pending_messages(Key, Acc, MarkerIds); false -> pending_messages(Key, Acc, MarkerIds);
true -> pending_messages(Key, [{PrevMsgId, PrevSTopic}|Acc], MarkerIds) true -> pending_messages(Key, [{PrevMsgId, PrevSTopic} | Acc], MarkerIds)
end; end;
_What -> %% Next sessionID or '$end_of_table' %% Next sessionID or '$end_of_table'
_What ->
case PrevTag =:= ?UNDELIVERED of case PrevTag =:= ?UNDELIVERED of
false -> {lists:reverse(Acc), MarkerIds}; false -> {lists:reverse(Acc), MarkerIds};
true -> {lists:reverse([{PrevMsgId, PrevSTopic}|Acc]), MarkerIds} true -> {lists:reverse([{PrevMsgId, PrevSTopic} | Acc]), MarkerIds}
end end
end. end.
@ -495,16 +530,20 @@ gc_traverse({S, _MsgID, <<>>, ?MARKER} = Key, SessionID, Abandoned, Fun) ->
ok = Fun(marker, Key), ok = Fun(marker, Key),
NewAbandoned = S =:= SessionID andalso Abandoned, NewAbandoned = S =:= SessionID andalso Abandoned,
gc_traverse(next_session_message(Key), S, NewAbandoned, Fun); gc_traverse(next_session_message(Key), S, NewAbandoned, Fun);
gc_traverse({S, _MsgID, _STopic, _Tag} = Key, SessionID, Abandoned, Fun) when Abandoned andalso gc_traverse({S, _MsgID, _STopic, _Tag} = Key, SessionID, Abandoned, Fun) when
S =:= SessionID -> Abandoned andalso
S =:= SessionID
->
%% Delete all messages from an abandoned session. %% Delete all messages from an abandoned session.
ok = Fun(delete, Key), ok = Fun(delete, Key),
gc_traverse(next_session_message(Key), S, Abandoned, Fun); gc_traverse(next_session_message(Key), S, Abandoned, Fun);
gc_traverse({S, MsgID, STopic, ?UNDELIVERED} = Key, SessionID, Abandoned, Fun) -> gc_traverse({S, MsgID, STopic, ?UNDELIVERED} = Key, SessionID, Abandoned, Fun) ->
case next_session_message(Key) of case next_session_message(Key) of
{S1, M, ST, ?DELIVERED} = NextKey when S1 =:= S andalso {S1, M, ST, ?DELIVERED} = NextKey when
MsgID =:= M andalso S1 =:= S andalso
STopic =:= ST -> MsgID =:= M andalso
STopic =:= ST
->
%% We have both markers for the same message/topic so it is safe to delete both. %% We have both markers for the same message/topic so it is safe to delete both.
ok = Fun(delete, Key), ok = Fun(delete, Key),
ok = Fun(delete, NextKey), ok = Fun(delete, NextKey),

View File

@ -23,13 +23,17 @@
-define(MSG_TAB_DISC, emqx_persistent_msg_disc). -define(MSG_TAB_DISC, emqx_persistent_msg_disc).
-define(MSG_TAB_RAM, emqx_persistent_msg_ram). -define(MSG_TAB_RAM, emqx_persistent_msg_ram).
-record(session_store, { client_id :: binary() -record(session_store, {
, expiry_interval :: non_neg_integer() client_id :: binary(),
, ts :: non_neg_integer() expiry_interval :: non_neg_integer(),
, session :: emqx_session:session()}). ts :: non_neg_integer(),
session :: emqx_session:session()
}).
-record(session_msg, {key :: emqx_persistent_session:sess_msg_key(), -record(session_msg, {
val = [] :: []}). key :: emqx_persistent_session:sess_msg_key(),
val = [] :: []
}).
-define(db_backend_key, [persistent_session_store, db_backend]). -define(db_backend_key, [persistent_session_store, db_backend]).
-define(is_enabled_key, [persistent_session_store, enabled]). -define(is_enabled_key, [persistent_session_store, enabled]).

View File

@ -18,20 +18,21 @@
-include("emqx_persistent_session.hrl"). -include("emqx_persistent_session.hrl").
-export([ first_message_id/0 -export([
, next_message_id/1 first_message_id/0,
, delete_message/1 next_message_id/1,
, first_session_message/0 delete_message/1,
, next_session_message/1 first_session_message/0,
, delete_session_message/1 next_session_message/1,
, put_session_store/1 delete_session_message/1,
, delete_session_store/1 put_session_store/1,
, lookup_session_store/1 delete_session_store/1,
, put_session_message/1 lookup_session_store/1,
, put_message/1 put_session_message/1,
, get_message/1 put_message/1,
, ro_transaction/1 get_message/1,
]). ro_transaction/1
]).
first_message_id() -> first_message_id() ->
'$end_of_table'. '$end_of_table'.
@ -73,4 +74,3 @@ get_message(_MsgId) ->
ro_transaction(Fun) -> ro_transaction(Fun) ->
Fun(). Fun().

View File

@ -23,17 +23,19 @@
-export([start_link/0]). -export([start_link/0]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
]). terminate/2
]).
-ifdef(TEST). -ifdef(TEST).
-export([ session_gc_worker/2 -export([
, message_gc_worker/0 session_gc_worker/2,
]). message_gc_worker/0
]).
-endif. -endif.
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
@ -85,14 +87,18 @@ terminate(_Reason, _State) ->
start_session_gc_timer(State) -> start_session_gc_timer(State) ->
Interval = emqx_config:get([persistent_session_store, session_message_gc_interval]), Interval = emqx_config:get([persistent_session_store, session_message_gc_interval]),
State#{ session_gc_timer => erlang:start_timer(Interval, self(), session_gc_timeout)}. State#{session_gc_timer => erlang:start_timer(Interval, self(), session_gc_timeout)}.
session_gc_timeout(Ref, #{ session_gc_timer := R } = State) when R =:= Ref -> session_gc_timeout(Ref, #{session_gc_timer := R} = State) when R =:= Ref ->
%% Prevent overlapping processes. %% Prevent overlapping processes.
GCPid = maps:get(session_gc_pid, State, undefined), GCPid = maps:get(session_gc_pid, State, undefined),
case GCPid =/= undefined andalso erlang:is_process_alive(GCPid) of case GCPid =/= undefined andalso erlang:is_process_alive(GCPid) of
true -> start_session_gc_timer(State); true ->
false -> start_session_gc_timer(State#{ session_gc_pid => proc_lib:spawn_link(fun session_gc_worker/0)}) start_session_gc_timer(State);
false ->
start_session_gc_timer(State#{
session_gc_pid => proc_lib:spawn_link(fun session_gc_worker/0)
})
end; end;
session_gc_timeout(_Ref, State) -> session_gc_timeout(_Ref, State) ->
State. State.
@ -105,13 +111,13 @@ session_gc_worker(delete, Key) ->
session_gc_worker(marker, Key) -> session_gc_worker(marker, Key) ->
TS = emqx_persistent_session:session_message_info(timestamp, Key), TS = emqx_persistent_session:session_message_info(timestamp, Key),
case TS + ?MARKER_GRACE_PERIOD < erlang:system_time(microsecond) of case TS + ?MARKER_GRACE_PERIOD < erlang:system_time(microsecond) of
true -> emqx_persistent_session:delete_session_message(Key); true -> emqx_persistent_session:delete_session_message(Key);
false -> ok false -> ok
end; end;
session_gc_worker(abandoned, Key) -> session_gc_worker(abandoned, Key) ->
TS = emqx_persistent_session:session_message_info(timestamp, Key), TS = emqx_persistent_session:session_message_info(timestamp, Key),
case TS + ?ABANDONED_GRACE_PERIOD < erlang:system_time(microsecond) of case TS + ?ABANDONED_GRACE_PERIOD < erlang:system_time(microsecond) of
true -> emqx_persistent_session:delete_session_message(Key); true -> emqx_persistent_session:delete_session_message(Key);
false -> ok false -> ok
end. end.
@ -124,14 +130,18 @@ session_gc_worker(abandoned, Key) ->
%% We sacrifice space for simplicity at this point. %% We sacrifice space for simplicity at this point.
start_message_gc_timer(State) -> start_message_gc_timer(State) ->
Interval = emqx_config:get([persistent_session_store, session_message_gc_interval]), Interval = emqx_config:get([persistent_session_store, session_message_gc_interval]),
State#{ message_gc_timer => erlang:start_timer(Interval, self(), message_gc_timeout)}. State#{message_gc_timer => erlang:start_timer(Interval, self(), message_gc_timeout)}.
message_gc_timeout(Ref, #{ message_gc_timer := R } = State) when R =:= Ref -> message_gc_timeout(Ref, #{message_gc_timer := R} = State) when R =:= Ref ->
%% Prevent overlapping processes. %% Prevent overlapping processes.
GCPid = maps:get(message_gc_pid, State, undefined), GCPid = maps:get(message_gc_pid, State, undefined),
case GCPid =/= undefined andalso erlang:is_process_alive(GCPid) of case GCPid =/= undefined andalso erlang:is_process_alive(GCPid) of
true -> start_message_gc_timer(State); true ->
false -> start_message_gc_timer(State#{ message_gc_pid => proc_lib:spawn_link(fun message_gc_worker/0)}) start_message_gc_timer(State);
false ->
start_message_gc_timer(State#{
message_gc_pid => proc_lib:spawn_link(fun message_gc_worker/0)
})
end; end;
message_gc_timeout(_Ref, State) -> message_gc_timeout(_Ref, State) ->
State. State.

View File

@ -19,48 +19,60 @@
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_persistent_session.hrl"). -include("emqx_persistent_session.hrl").
-export([ create_tables/0 -export([
, first_message_id/0 create_tables/0,
, next_message_id/1 first_message_id/0,
, delete_message/1 next_message_id/1,
, first_session_message/0 delete_message/1,
, next_session_message/1 first_session_message/0,
, delete_session_message/1 next_session_message/1,
, put_session_store/1 delete_session_message/1,
, delete_session_store/1 put_session_store/1,
, lookup_session_store/1 delete_session_store/1,
, put_session_message/1 lookup_session_store/1,
, put_message/1 put_session_message/1,
, get_message/1 put_message/1,
, ro_transaction/1 get_message/1,
]). ro_transaction/1
]).
create_tables() -> create_tables() ->
ok = mria:create_table(?SESSION_STORE_DISC, [ ok = mria:create_table(?SESSION_STORE_DISC, [
{type, set}, {type, set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, session_store}, {record_name, session_store},
{attributes, record_info(fields, session_store)}, {attributes, record_info(fields, session_store)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}]), {storage_properties, [{ets, [{read_concurrency, true}]}]}
]),
ok = mria:create_table(?SESS_MSG_TAB_DISC, [ ok = mria:create_table(?SESS_MSG_TAB_DISC, [
{type, ordered_set}, {type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, session_msg}, {record_name, session_msg},
{attributes, record_info(fields, session_msg)}, {attributes, record_info(fields, session_msg)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]), {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]),
ok = mria:create_table(?MSG_TAB_DISC, [ ok = mria:create_table(?MSG_TAB_DISC, [
{type, ordered_set}, {type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, message}, {record_name, message},
{attributes, record_info(fields, message)}, {attributes, record_info(fields, message)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]). {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]).
first_session_message() -> first_session_message() ->
mnesia:dirty_first(?SESS_MSG_TAB_DISC). mnesia:dirty_first(?SESS_MSG_TAB_DISC).
@ -107,4 +119,3 @@ get_message(MsgId) ->
ro_transaction(Fun) -> ro_transaction(Fun) ->
{atomic, Res} = mria:ro_transaction(?PERSISTENT_SESSION_SHARD, Fun), {atomic, Res} = mria:ro_transaction(?PERSISTENT_SESSION_SHARD, Fun),
Res. Res.

View File

@ -19,48 +19,60 @@
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_persistent_session.hrl"). -include("emqx_persistent_session.hrl").
-export([ create_tables/0 -export([
, first_message_id/0 create_tables/0,
, next_message_id/1 first_message_id/0,
, delete_message/1 next_message_id/1,
, first_session_message/0 delete_message/1,
, next_session_message/1 first_session_message/0,
, delete_session_message/1 next_session_message/1,
, put_session_store/1 delete_session_message/1,
, delete_session_store/1 put_session_store/1,
, lookup_session_store/1 delete_session_store/1,
, put_session_message/1 lookup_session_store/1,
, put_message/1 put_session_message/1,
, get_message/1 put_message/1,
, ro_transaction/1 get_message/1,
]). ro_transaction/1
]).
create_tables() -> create_tables() ->
ok = mria:create_table(?SESSION_STORE_RAM, [ ok = mria:create_table(?SESSION_STORE_RAM, [
{type, set}, {type, set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, session_store}, {record_name, session_store},
{attributes, record_info(fields, session_store)}, {attributes, record_info(fields, session_store)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}]), {storage_properties, [{ets, [{read_concurrency, true}]}]}
]),
ok = mria:create_table(?SESS_MSG_TAB_RAM, [ ok = mria:create_table(?SESS_MSG_TAB_RAM, [
{type, ordered_set}, {type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, session_msg}, {record_name, session_msg},
{attributes, record_info(fields, session_msg)}, {attributes, record_info(fields, session_msg)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]), {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]),
ok = mria:create_table(?MSG_TAB_RAM, [ ok = mria:create_table(?MSG_TAB_RAM, [
{type, ordered_set}, {type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, message}, {record_name, message},
{attributes, record_info(fields, message)}, {attributes, record_info(fields, message)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]). {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]).
first_session_message() -> first_session_message() ->
mnesia:dirty_first(?SESS_MSG_TAB_RAM). mnesia:dirty_first(?SESS_MSG_TAB_RAM).
@ -107,4 +119,3 @@ get_message(MsgId) ->
ro_transaction(Fun) -> ro_transaction(Fun) ->
{atomic, Res} = mria:ro_transaction(?PERSISTENT_SESSION_SHARD, Fun), {atomic, Res} = mria:ro_transaction(?PERSISTENT_SESSION_SHARD, Fun),
Res. Res.

View File

@ -30,31 +30,40 @@ init([]) ->
SessionTab = emqx_session_router:create_init_tab(), SessionTab = emqx_session_router:create_init_tab(),
%% Resume worker sup %% Resume worker sup
ResumeSup = #{id => router_worker_sup, ResumeSup = #{
start => {emqx_session_router_worker_sup, start_link, [SessionTab]}, id => router_worker_sup,
restart => permanent, start => {emqx_session_router_worker_sup, start_link, [SessionTab]},
shutdown => 2000, restart => permanent,
type => supervisor, shutdown => 2000,
modules => [emqx_session_router_worker_sup]}, type => supervisor,
modules => [emqx_session_router_worker_sup]
},
SessionRouterPool = emqx_pool_sup:spec(session_router_pool, SessionRouterPool = emqx_pool_sup:spec(
[session_router_pool, hash, session_router_pool,
{emqx_session_router, start_link, []}]), [
session_router_pool,
hash,
{emqx_session_router, start_link, []}
]
),
GCWorker = child_spec(emqx_persistent_session_gc, worker), GCWorker = child_spec(emqx_persistent_session_gc, worker),
Spec = #{ strategy => one_for_all Spec = #{
, intensity => 0 strategy => one_for_all,
, period => 1 intensity => 0,
}, period => 1
},
{ok, {Spec, [ResumeSup, SessionRouterPool, GCWorker]}}. {ok, {Spec, [ResumeSup, SessionRouterPool, GCWorker]}}.
child_spec(Mod, worker) -> child_spec(Mod, worker) ->
#{id => Mod, #{
start => {Mod, start_link, []}, id => Mod,
restart => permanent, start => {Mod, start_link, []},
shutdown => 15000, restart => permanent,
type => worker, shutdown => 15000,
modules => [Mod] type => worker,
}. modules => [Mod]
}.

View File

@ -20,21 +20,23 @@
-export([new/0]). -export([new/0]).
-export([ monitor/2 -export([
, monitor/3 monitor/2,
, demonitor/2 monitor/3,
]). demonitor/2
]).
-export([ find/2 -export([
, erase/2 find/2,
, erase_all/2 erase/2,
]). erase_all/2
]).
-export([count/1]). -export([count/1]).
-export_type([pmon/0]). -export_type([pmon/0]).
-opaque(pmon() :: {?MODULE, map()}). -opaque pmon() :: {?MODULE, map()}.
-define(PMON(Map), {?MODULE, Map}). -define(PMON(Map), {?MODULE, Map}).
@ -42,55 +44,61 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(new() -> pmon()). -spec new() -> pmon().
new() -> ?PMON(maps:new()). new() -> ?PMON(maps:new()).
-spec(monitor(pid(), pmon()) -> pmon()). -spec monitor(pid(), pmon()) -> pmon().
monitor(Pid, PMon) -> monitor(Pid, PMon) ->
?MODULE:monitor(Pid, undefined, PMon). ?MODULE:monitor(Pid, undefined, PMon).
-spec(monitor(pid(), term(), pmon()) -> pmon()). -spec monitor(pid(), term(), pmon()) -> pmon().
monitor(Pid, Val, PMon = ?PMON(Map)) -> monitor(Pid, Val, PMon = ?PMON(Map)) ->
case maps:is_key(Pid, Map) of case maps:is_key(Pid, Map) of
true -> PMon; true ->
PMon;
false -> false ->
Ref = erlang:monitor(process, Pid), Ref = erlang:monitor(process, Pid),
?PMON(maps:put(Pid, {Ref, Val}, Map)) ?PMON(maps:put(Pid, {Ref, Val}, Map))
end. end.
-spec(demonitor(pid(), pmon()) -> pmon()). -spec demonitor(pid(), pmon()) -> pmon().
demonitor(Pid, PMon = ?PMON(Map)) -> demonitor(Pid, PMon = ?PMON(Map)) ->
case maps:find(Pid, Map) of case maps:find(Pid, Map) of
{ok, {Ref, _Val}} -> {ok, {Ref, _Val}} ->
%% flush %% flush
_ = erlang:demonitor(Ref, [flush]), _ = erlang:demonitor(Ref, [flush]),
?PMON(maps:remove(Pid, Map)); ?PMON(maps:remove(Pid, Map));
error -> PMon error ->
PMon
end. end.
-spec(find(pid(), pmon()) -> error | {ok, term()}). -spec find(pid(), pmon()) -> error | {ok, term()}.
find(Pid, ?PMON(Map)) -> find(Pid, ?PMON(Map)) ->
case maps:find(Pid, Map) of case maps:find(Pid, Map) of
{ok, {_Ref, Val}} -> {ok, {_Ref, Val}} ->
{ok, Val}; {ok, Val};
error -> error error ->
error
end. end.
-spec(erase(pid(), pmon()) -> pmon()). -spec erase(pid(), pmon()) -> pmon().
erase(Pid, ?PMON(Map)) -> erase(Pid, ?PMON(Map)) ->
?PMON(maps:remove(Pid, Map)). ?PMON(maps:remove(Pid, Map)).
-spec(erase_all([pid()], pmon()) -> {[{pid(), term()}], pmon()}). -spec erase_all([pid()], pmon()) -> {[{pid(), term()}], pmon()}.
erase_all(Pids, PMon0) -> erase_all(Pids, PMon0) ->
lists:foldl( lists:foldl(
fun(Pid, {Acc, PMon}) -> fun(Pid, {Acc, PMon}) ->
case find(Pid, PMon) of case find(Pid, PMon) of
{ok, Val} -> {ok, Val} ->
{[{Pid, Val}|Acc], erase(Pid, PMon)}; {[{Pid, Val} | Acc], erase(Pid, PMon)};
error -> {Acc, PMon} error ->
end {Acc, PMon}
end, {[], PMon0}, Pids). end
end,
{[], PMon0},
Pids
).
-spec(count(pmon()) -> non_neg_integer()). -spec count(pmon()) -> non_neg_integer().
count(?PMON(Map)) -> maps:size(Map). count(?PMON(Map)) -> maps:size(Map).

View File

@ -21,49 +21,54 @@
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
%% APIs %% APIs
-export([start_link/2]). -export([start_link/2]).
-export([ submit/1 -export([
, submit/2 submit/1,
, async_submit/1 submit/2,
, async_submit/2 async_submit/1,
]). async_submit/2
]).
-ifdef(TEST). -ifdef(TEST).
-export([worker/0, flush_async_tasks/0]). -export([worker/0, flush_async_tasks/0]).
-endif. -endif.
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-define(POOL, ?MODULE). -define(POOL, ?MODULE).
-type(task() :: fun() | mfa() | {fun(), Args :: list(any())}). -type task() :: fun() | mfa() | {fun(), Args :: list(any())}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Start pool. %% @doc Start pool.
-spec(start_link(atom(), pos_integer()) -> startlink_ret()). -spec start_link(atom(), pos_integer()) -> startlink_ret().
start_link(Pool, Id) -> start_link(Pool, Id) ->
gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, gen_server:start_link(
?MODULE, [Pool, Id], [{hibernate_after, 1000}]). {local, emqx_misc:proc_name(?MODULE, Id)},
?MODULE,
[Pool, Id],
[{hibernate_after, 1000}]
).
%% @doc Submit work to the pool. %% @doc Submit work to the pool.
-spec(submit(task()) -> any()). -spec submit(task()) -> any().
submit(Task) -> submit(Task) ->
call({submit, Task}). call({submit, Task}).
-spec(submit(fun(), list(any())) -> any()). -spec submit(fun(), list(any())) -> any().
submit(Fun, Args) -> submit(Fun, Args) ->
call({submit, {Fun, Args}}). call({submit, {Fun, Args}}).
@ -72,11 +77,11 @@ call(Req) ->
gen_server:call(worker(), Req, infinity). gen_server:call(worker(), Req, infinity).
%% @doc Submit work to the pool asynchronously. %% @doc Submit work to the pool asynchronously.
-spec(async_submit(task()) -> ok). -spec async_submit(task()) -> ok.
async_submit(Task) -> async_submit(Task) ->
cast({async_submit, Task}). cast({async_submit, Task}).
-spec(async_submit(fun(), list(any())) -> ok). -spec async_submit(fun(), list(any())) -> ok.
async_submit(Fun, Args) -> async_submit(Fun, Args) ->
cast({async_submit, {Fun, Args}}). cast({async_submit, {Fun, Args}}).
@ -98,22 +103,23 @@ init([Pool, Id]) ->
handle_call({submit, Task}, _From, State) -> handle_call({submit, Task}, _From, State) ->
{reply, catch run(Task), State}; {reply, catch run(Task), State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.
handle_cast({async_submit, Task}, State) -> handle_cast({async_submit, Task}, State) ->
try run(Task) try
catch Error:Reason:Stacktrace -> run(Task)
?SLOG(error, #{msg => "async_submit_error", catch
exception => Error, Error:Reason:Stacktrace ->
reason => Reason, ?SLOG(error, #{
stacktrace => Stacktrace msg => "async_submit_error",
}) exception => Error,
reason => Reason,
stacktrace => Stacktrace
})
end, end,
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
@ -149,5 +155,12 @@ flush_async_tasks() ->
Self = self(), Self = self(),
L = lists:seq(1, 997), L = lists:seq(1, 997),
lists:foreach(fun(I) -> emqx_pool:async_submit(fun() -> Self ! {done, Ref, I} end, []) end, L), lists:foreach(fun(I) -> emqx_pool:async_submit(fun() -> Self ! {done, Ref, I} end, []) end, L),
lists:foreach(fun(I) -> receive {done, Ref, I} -> ok end end, L). lists:foreach(
fun(I) ->
receive
{done, Ref, I} -> ok
end
end,
L
).
-endif. -endif.

View File

@ -22,64 +22,73 @@
-export([spec/1, spec/2]). -export([spec/1, spec/2]).
-export([ start_link/0 -export([
, start_link/3 start_link/0,
, start_link/4 start_link/3,
]). start_link/4
]).
-export([init/1]). -export([init/1]).
-define(POOL, emqx_pool). -define(POOL, emqx_pool).
-spec(spec(list()) -> supervisor:child_spec()). -spec spec(list()) -> supervisor:child_spec().
spec(Args) -> spec(Args) ->
spec(pool_sup, Args). spec(pool_sup, Args).
-spec(spec(any(), list()) -> supervisor:child_spec()). -spec spec(any(), list()) -> supervisor:child_spec().
spec(ChildId, Args) -> spec(ChildId, Args) ->
#{id => ChildId, #{
start => {?MODULE, start_link, Args}, id => ChildId,
restart => transient, start => {?MODULE, start_link, Args},
shutdown => infinity, restart => transient,
type => supervisor, shutdown => infinity,
modules => [?MODULE]}. type => supervisor,
modules => [?MODULE]
}.
%% @doc Start the default pool supervisor. %% @doc Start the default pool supervisor.
start_link() -> start_link() ->
start_link(?POOL, random, {?POOL, start_link, []}). start_link(?POOL, random, {?POOL, start_link, []}).
-spec(start_link(atom() | tuple(), atom(), mfargs()) -spec start_link(atom() | tuple(), atom(), mfargs()) ->
-> {ok, pid()} | {error, term()}). {ok, pid()} | {error, term()}.
start_link(Pool, Type, MFA) -> start_link(Pool, Type, MFA) ->
start_link(Pool, Type, emqx_vm:schedulers(), MFA). start_link(Pool, Type, emqx_vm:schedulers(), MFA).
-spec(start_link(atom() | tuple(), atom(), pos_integer(), mfargs()) -spec start_link(atom() | tuple(), atom(), pos_integer(), mfargs()) ->
-> {ok, pid()} | {error, term()}). {ok, pid()} | {error, term()}.
start_link(Pool, Type, Size, MFA) -> start_link(Pool, Type, Size, MFA) ->
supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]). supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]).
init([Pool, Type, Size, {M, F, Args}]) -> init([Pool, Type, Size, {M, F, Args}]) ->
ok = ensure_pool(Pool, Type, [{size, Size}]), ok = ensure_pool(Pool, Type, [{size, Size}]),
{ok, {{one_for_one, 10, 3600}, [ {ok,
begin {{one_for_one, 10, 3600}, [
ensure_pool_worker(Pool, {Pool, I}, I), begin
#{id => {M, I}, ensure_pool_worker(Pool, {Pool, I}, I),
start => {M, F, [Pool, I | Args]}, #{
restart => transient, id => {M, I},
shutdown => 5000, start => {M, F, [Pool, I | Args]},
type => worker, restart => transient,
modules => [M]} shutdown => 5000,
end || I <- lists:seq(1, Size)]}}. type => worker,
modules => [M]
}
end
|| I <- lists:seq(1, Size)
]}}.
ensure_pool(Pool, Type, Opts) -> ensure_pool(Pool, Type, Opts) ->
try gproc_pool:new(Pool, Type, Opts) try
gproc_pool:new(Pool, Type, Opts)
catch catch
error:exists -> ok error:exists -> ok
end. end.
ensure_pool_worker(Pool, Name, Slot) -> ensure_pool_worker(Pool, Name, Slot) ->
try gproc_pool:add_worker(Pool, Name, Slot) try
gproc_pool:add_worker(Pool, Name, Slot)
catch catch
error:exists -> ok error:exists -> ok
end. end.

View File

@ -39,63 +39,68 @@
-module(emqx_pqueue). -module(emqx_pqueue).
-export([ new/0 -export([
, is_queue/1 new/0,
, is_empty/1 is_queue/1,
, len/1 is_empty/1,
, plen/2 len/1,
, to_list/1 plen/2,
, from_list/1 to_list/1,
, in/2 from_list/1,
, in/3 in/2,
, out/1 in/3,
, out/2 out/1,
, out_p/1 out/2,
, join/2 out_p/1,
, filter/2 join/2,
, fold/3 filter/2,
, highest/1 fold/3,
, shift/1 highest/1,
]). shift/1
]).
-export_type([q/0]). -export_type([q/0]).
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
-type(priority() :: integer() | 'infinity'). -type priority() :: integer() | 'infinity'.
-type(squeue() :: {queue, [any()], [any()], non_neg_integer()}). -type squeue() :: {queue, [any()], [any()], non_neg_integer()}.
-type(pqueue() :: squeue() | {pqueue, [{priority(), squeue()}]}). -type pqueue() :: squeue() | {pqueue, [{priority(), squeue()}]}.
-type(q() :: pqueue()). -type q() :: pqueue().
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
-spec(new() -> pqueue()). -spec new() -> pqueue().
new() -> new() ->
{queue, [], [], 0}. {queue, [], [], 0}.
-spec(is_queue(any()) -> boolean()). -spec is_queue(any()) -> boolean().
is_queue({queue, R, F, L}) when is_list(R), is_list(F), is_integer(L) -> is_queue({queue, R, F, L}) when is_list(R), is_list(F), is_integer(L) ->
true; true;
is_queue({pqueue, Queues}) when is_list(Queues) -> is_queue({pqueue, Queues}) when is_list(Queues) ->
lists:all(fun ({infinity, Q}) -> is_queue(Q); lists:all(
({P, Q}) -> is_integer(P) andalso is_queue(Q) fun
end, Queues); ({infinity, Q}) -> is_queue(Q);
({P, Q}) -> is_integer(P) andalso is_queue(Q)
end,
Queues
);
is_queue(_) -> is_queue(_) ->
false. false.
-spec(is_empty(pqueue()) -> boolean()). -spec is_empty(pqueue()) -> boolean().
is_empty({queue, [], [], 0}) -> is_empty({queue, [], [], 0}) ->
true; true;
is_empty(_) -> is_empty(_) ->
false. false.
-spec(len(pqueue()) -> non_neg_integer()). -spec len(pqueue()) -> non_neg_integer().
len({queue, _R, _F, L}) -> len({queue, _R, _F, L}) ->
L; L;
len({pqueue, Queues}) -> len({pqueue, Queues}) ->
lists:sum([len(Q) || {_, Q} <- Queues]). lists:sum([len(Q) || {_, Q} <- Queues]).
-spec(plen(priority(), pqueue()) -> non_neg_integer()). -spec plen(priority(), pqueue()) -> non_neg_integer().
plen(0, {queue, _R, _F, L}) -> plen(0, {queue, _R, _F, L}) ->
L; L;
plen(_, {queue, _R, _F, _}) -> plen(_, {queue, _R, _F, _}) ->
@ -103,84 +108,95 @@ plen(_, {queue, _R, _F, _}) ->
plen(P, {pqueue, Queues}) -> plen(P, {pqueue, Queues}) ->
case lists:keysearch(maybe_negate_priority(P), 1, Queues) of case lists:keysearch(maybe_negate_priority(P), 1, Queues) of
{value, {_, Q}} -> len(Q); {value, {_, Q}} -> len(Q);
false -> 0 false -> 0
end. end.
-spec(to_list(pqueue()) -> [{priority(), any()}]). -spec to_list(pqueue()) -> [{priority(), any()}].
to_list({queue, In, Out, _Len}) when is_list(In), is_list(Out) -> to_list({queue, In, Out, _Len}) when is_list(In), is_list(Out) ->
[{0, V} || V <- Out ++ lists:reverse(In, [])]; [{0, V} || V <- Out ++ lists:reverse(In, [])];
to_list({pqueue, Queues}) -> to_list({pqueue, Queues}) ->
[{maybe_negate_priority(P), V} || {P, Q} <- Queues, [
{0, V} <- to_list(Q)]. {maybe_negate_priority(P), V}
|| {P, Q} <- Queues,
{0, V} <- to_list(Q)
].
-spec(from_list([{priority(), any()}]) -> pqueue()). -spec from_list([{priority(), any()}]) -> pqueue().
from_list(L) -> from_list(L) ->
lists:foldl(fun ({P, E}, Q) -> in(E, P, Q) end, new(), L). lists:foldl(fun({P, E}, Q) -> in(E, P, Q) end, new(), L).
-spec(in(any(), pqueue()) -> pqueue()). -spec in(any(), pqueue()) -> pqueue().
in(Item, Q) -> in(Item, Q) ->
in(Item, 0, Q). in(Item, 0, Q).
-spec(in(any(), priority(), pqueue()) -> pqueue()). -spec in(any(), priority(), pqueue()) -> pqueue().
in(X, 0, {queue, [_] = In, [], 1}) -> in(X, 0, {queue, [_] = In, [], 1}) ->
{queue, [X], In, 2}; {queue, [X], In, 2};
in(X, 0, {queue, In, Out, Len}) when is_list(In), is_list(Out) -> in(X, 0, {queue, In, Out, Len}) when is_list(In), is_list(Out) ->
{queue, [X|In], Out, Len + 1}; {queue, [X | In], Out, Len + 1};
in(X, Priority, _Q = {queue, [], [], 0}) -> in(X, Priority, _Q = {queue, [], [], 0}) ->
in(X, Priority, {pqueue, []}); in(X, Priority, {pqueue, []});
in(X, Priority, Q = {queue, _, _, _}) -> in(X, Priority, Q = {queue, _, _, _}) ->
in(X, Priority, {pqueue, [{0, Q}]}); in(X, Priority, {pqueue, [{0, Q}]});
in(X, Priority, {pqueue, Queues}) -> in(X, Priority, {pqueue, Queues}) ->
P = maybe_negate_priority(Priority), P = maybe_negate_priority(Priority),
{pqueue, case lists:keysearch(P, 1, Queues) of {pqueue,
{value, {_, Q}} -> case lists:keysearch(P, 1, Queues) of
lists:keyreplace(P, 1, Queues, {P, in(X, Q)}); {value, {_, Q}} ->
false when P == infinity -> lists:keyreplace(P, 1, Queues, {P, in(X, Q)});
[{P, {queue, [X], [], 1}} | Queues]; false when P == infinity ->
false -> [{P, {queue, [X], [], 1}} | Queues];
case Queues of false ->
[{infinity, InfQueue} | Queues1] -> case Queues of
[{infinity, InfQueue} | [{infinity, InfQueue} | Queues1] ->
lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues1])]; [
_ -> {infinity, InfQueue}
lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues]) | lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues1])
end ];
end}. _ ->
lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues])
end
end}.
-spec(out(pqueue()) -> {empty | {value, any()}, pqueue()}). -spec out(pqueue()) -> {empty | {value, any()}, pqueue()}.
out({queue, [], [], 0} = Q) -> out({queue, [], [], 0} = Q) ->
{empty, Q}; {empty, Q};
out({queue, [V], [], 1}) -> out({queue, [V], [], 1}) ->
{{value, V}, {queue, [], [], 0}}; {{value, V}, {queue, [], [], 0}};
out({queue, [Y|In], [], Len}) -> out({queue, [Y | In], [], Len}) ->
[V|Out] = lists:reverse(In, []), [V | Out] = lists:reverse(In, []),
{{value, V}, {queue, [Y], Out, Len - 1}}; {{value, V}, {queue, [Y], Out, Len - 1}};
out({queue, In, [V], Len}) when is_list(In) -> out({queue, In, [V], Len}) when is_list(In) ->
{{value,V}, r2f(In, Len - 1)}; {{value, V}, r2f(In, Len - 1)};
out({queue, In,[V|Out], Len}) when is_list(In) -> out({queue, In, [V | Out], Len}) when is_list(In) ->
{{value, V}, {queue, In, Out, Len - 1}}; {{value, V}, {queue, In, Out, Len - 1}};
out({pqueue, [{P, Q} | Queues]}) -> out({pqueue, [{P, Q} | Queues]}) ->
{R, Q1} = out(Q), {R, Q1} = out(Q),
NewQ = case is_empty(Q1) of NewQ =
true -> case Queues of case is_empty(Q1) of
[] -> {queue, [], [], 0}; true ->
[{0, OnlyQ}] -> OnlyQ; case Queues of
[_|_] -> {pqueue, Queues} [] -> {queue, [], [], 0};
end; [{0, OnlyQ}] -> OnlyQ;
false -> {pqueue, [{P, Q1} | Queues]} [_ | _] -> {pqueue, Queues}
end, end;
false ->
{pqueue, [{P, Q1} | Queues]}
end,
{R, NewQ}. {R, NewQ}.
-spec(shift(pqueue()) -> pqueue()). -spec shift(pqueue()) -> pqueue().
shift(Q = {queue, _, _, _}) -> shift(Q = {queue, _, _, _}) ->
Q; Q;
shift({pqueue, []}) -> shift({pqueue, []}) ->
{pqueue, []}; %% Shouldn't happen? %% Shouldn't happen?
shift({pqueue, [Hd|Rest]}) -> {pqueue, []};
{pqueue, Rest ++ [Hd]}. %% Let's hope there are not many priorities. shift({pqueue, [Hd | Rest]}) ->
%% Let's hope there are not many priorities.
{pqueue, Rest ++ [Hd]}.
-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)).
out(0, {queue, _, _, _} = Q) -> out(0, {queue, _, _, _} = Q) ->
@ -192,25 +208,28 @@ out(Priority, {pqueue, Queues}) ->
case lists:keysearch(P, 1, Queues) of case lists:keysearch(P, 1, Queues) of
{value, {_, Q}} -> {value, {_, Q}} ->
{R, Q1} = out(Q), {R, Q1} = out(Q),
Queues1 = case is_empty(Q1) of Queues1 =
true -> lists:keydelete(P, 1, Queues); case is_empty(Q1) of
false -> lists:keyreplace(P, 1, Queues, {P, Q1}) true -> lists:keydelete(P, 1, Queues);
end, false -> lists:keyreplace(P, 1, Queues, {P, Q1})
{R, case Queues1 of end,
[] -> {queue, [], [], 0}; {R,
case Queues1 of
[] -> {queue, [], [], 0};
[{0, OnlyQ}] -> OnlyQ; [{0, OnlyQ}] -> OnlyQ;
[_|_] -> {pqueue, Queues1} [_ | _] -> {pqueue, Queues1}
end}; end};
false -> false ->
{empty, {pqueue, Queues}} {empty, {pqueue, Queues}}
end. end.
add_p(R, P) -> case R of add_p(R, P) ->
{empty, Q} -> {empty, Q}; case R of
{{value, V}, Q} -> {{value, V, P}, Q} {empty, Q} -> {empty, Q};
end. {{value, V}, Q} -> {{value, V, P}, Q}
end.
-spec(join(pqueue(), pqueue()) -> pqueue()). -spec join(pqueue(), pqueue()) -> pqueue().
join(A, {queue, [], [], 0}) -> join(A, {queue, [], [], 0}) ->
A; A;
join({queue, [], [], 0}, B) -> join({queue, [], [], 0}, B) ->
@ -219,21 +238,23 @@ join({queue, AIn, AOut, ALen}, {queue, BIn, BOut, BLen}) ->
{queue, BIn, AOut ++ lists:reverse(AIn, BOut), ALen + BLen}; {queue, BIn, AOut ++ lists:reverse(AIn, BOut), ALen + BLen};
join(A = {queue, _, _, _}, {pqueue, BPQ}) -> join(A = {queue, _, _, _}, {pqueue, BPQ}) ->
{Pre, Post} = {Pre, Post} =
lists:splitwith(fun ({P, _}) -> P < 0 orelse P == infinity end, BPQ), lists:splitwith(fun({P, _}) -> P < 0 orelse P == infinity end, BPQ),
Post1 = case Post of Post1 =
[] -> [ {0, A} ]; case Post of
[ {0, ZeroQueue} | Rest ] -> [ {0, join(A, ZeroQueue)} | Rest ]; [] -> [{0, A}];
_ -> [ {0, A} | Post ] [{0, ZeroQueue} | Rest] -> [{0, join(A, ZeroQueue)} | Rest];
end, _ -> [{0, A} | Post]
end,
{pqueue, Pre ++ Post1}; {pqueue, Pre ++ Post1};
join({pqueue, APQ}, B = {queue, _, _, _}) -> join({pqueue, APQ}, B = {queue, _, _, _}) ->
{Pre, Post} = {Pre, Post} =
lists:splitwith(fun ({P, _}) -> P < 0 orelse P == infinity end, APQ), lists:splitwith(fun({P, _}) -> P < 0 orelse P == infinity end, APQ),
Post1 = case Post of Post1 =
[] -> [ {0, B} ]; case Post of
[ {0, ZeroQueue} | Rest ] -> [ {0, join(ZeroQueue, B)} | Rest ]; [] -> [{0, B}];
_ -> [ {0, B} | Post ] [{0, ZeroQueue} | Rest] -> [{0, join(ZeroQueue, B)} | Rest];
end, _ -> [{0, B} | Post]
end,
{pqueue, Pre ++ Post1}; {pqueue, Pre ++ Post1};
join({pqueue, APQ}, {pqueue, BPQ}) -> join({pqueue, APQ}, {pqueue, BPQ}) ->
{pqueue, merge(APQ, BPQ, [])}. {pqueue, merge(APQ, BPQ, [])}.
@ -242,36 +263,42 @@ merge([], BPQ, Acc) ->
lists:reverse(Acc, BPQ); lists:reverse(Acc, BPQ);
merge(APQ, [], Acc) -> merge(APQ, [], Acc) ->
lists:reverse(Acc, APQ); lists:reverse(Acc, APQ);
merge([{P, A}|As], [{P, B}|Bs], Acc) -> merge([{P, A} | As], [{P, B} | Bs], Acc) ->
merge(As, Bs, [ {P, join(A, B)} | Acc ]); merge(As, Bs, [{P, join(A, B)} | Acc]);
merge([{PA, A}|As], Bs = [{PB, _}|_], Acc) when PA < PB orelse PA == infinity -> merge([{PA, A} | As], Bs = [{PB, _} | _], Acc) when PA < PB orelse PA == infinity ->
merge(As, Bs, [ {PA, A} | Acc ]); merge(As, Bs, [{PA, A} | Acc]);
merge(As = [{_, _}|_], [{PB, B}|Bs], Acc) -> merge(As = [{_, _} | _], [{PB, B} | Bs], Acc) ->
merge(As, Bs, [ {PB, B} | Acc ]). merge(As, Bs, [{PB, B} | Acc]).
-spec(filter(fun ((any()) -> boolean()), pqueue()) -> pqueue()). -spec filter(fun((any()) -> boolean()), pqueue()) -> pqueue().
filter(Pred, Q) -> fold(fun(V, P, Acc) -> filter(Pred, Q) ->
case Pred(V) of fold(
true -> in(V, P, Acc); fun(V, P, Acc) ->
false -> Acc case Pred(V) of
end true -> in(V, P, Acc);
end, new(), Q). false -> Acc
end
end,
new(),
Q
).
-spec(fold(fun ((any(), priority(), A) -> A), A, pqueue()) -> A). -spec fold(fun((any(), priority(), A) -> A), A, pqueue()) -> A.
fold(Fun, Init, Q) -> case out_p(Q) of fold(Fun, Init, Q) ->
{empty, _Q} -> Init; case out_p(Q) of
{{value, V, P}, Q1} -> fold(Fun, Fun(V, P, Init), Q1) {empty, _Q} -> Init;
end. {{value, V, P}, Q1} -> fold(Fun, Fun(V, P, Init), Q1)
end.
-spec(highest(pqueue()) -> priority() | 'empty'). -spec highest(pqueue()) -> priority() | 'empty'.
highest({queue, [], [], 0}) -> empty; highest({queue, [], [], 0}) -> empty;
highest({queue, _, _, _}) -> 0; highest({queue, _, _, _}) -> 0;
highest({pqueue, [{P, _} | _]}) -> maybe_negate_priority(P). highest({pqueue, [{P, _} | _]}) -> maybe_negate_priority(P).
r2f([], 0) -> {queue, [], [], 0}; r2f([], 0) -> {queue, [], [], 0};
r2f([_] = R, 1) -> {queue, [], R, 1}; r2f([_] = R, 1) -> {queue, [], R, 1};
r2f([X,Y], 2) -> {queue, [X], [Y], 2}; r2f([X, Y], 2) -> {queue, [X], [Y], 2};
r2f([X,Y|R], L) -> {queue, [X,Y], lists:reverse(R, []), L}. 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.

View File

@ -17,15 +17,15 @@
-module(emqx_quic_connection). -module(emqx_quic_connection).
%% Callbacks %% Callbacks
-export([ init/1 -export([
, new_conn/2 init/1,
, connected/2 new_conn/2,
, shutdown/2 connected/2,
]). shutdown/2
]).
-type cb_state() :: map() | proplists:proplist(). -type cb_state() :: map() | proplists:proplist().
-spec init(cb_state()) -> cb_state(). -spec init(cb_state()) -> cb_state().
init(ConnOpts) when is_list(ConnOpts) -> init(ConnOpts) when is_list(ConnOpts) ->
init(maps:from_list(ConnOpts)); init(maps:from_list(ConnOpts));

View File

@ -18,18 +18,19 @@
-module(emqx_quic_stream). -module(emqx_quic_stream).
%% emqx transport Callbacks %% emqx transport Callbacks
-export([ type/1 -export([
, wait/1 type/1,
, getstat/2 wait/1,
, fast_close/1 getstat/2,
, ensure_ok_or_exit/2 fast_close/1,
, async_send/3 ensure_ok_or_exit/2,
, setopts/2 async_send/3,
, getopts/2 setopts/2,
, peername/1 getopts/2,
, sockname/1 peername/1,
, peercert/1 sockname/1,
]). peercert/1
]).
wait({ConnOwner, Conn}) -> wait({ConnOwner, Conn}) ->
{ok, Conn} = quicer:async_accept_stream(Conn, []), {ok, Conn} = quicer:async_accept_stream(Conn, []),
@ -62,28 +63,34 @@ getstat(Socket, Stats) ->
end. end.
setopts(Socket, Opts) -> setopts(Socket, Opts) ->
lists:foreach(fun({Opt, V}) when is_atom(Opt) -> lists:foreach(
quicer:setopt(Socket, Opt, V); fun
(Opt) when is_atom(Opt) -> ({Opt, V}) when is_atom(Opt) ->
quicer:setopt(Socket, Opt, true) quicer:setopt(Socket, Opt, V);
end, Opts), (Opt) when is_atom(Opt) ->
quicer:setopt(Socket, Opt, true)
end,
Opts
),
ok. ok.
getopts(_Socket, _Opts) -> getopts(_Socket, _Opts) ->
%% @todo %% @todo
{ok, [{high_watermark, 0}, {ok, [
{high_msgq_watermark, 0}, {high_watermark, 0},
{sndbuf, 0}, {high_msgq_watermark, 0},
{recbuf, 0}, {sndbuf, 0},
{buffer,80000}]}. {recbuf, 0},
{buffer, 80000}
]}.
fast_close(Stream) -> fast_close(Stream) ->
%% Stream might be closed already. %% Stream might be closed already.
_ = quicer:async_close_stream(Stream), _ = quicer:async_close_stream(Stream),
ok. ok.
-spec(ensure_ok_or_exit(atom(), list(term())) -> term()). -spec ensure_ok_or_exit(atom(), list(term())) -> term().
ensure_ok_or_exit(Fun, Args = [Sock|_]) when is_atom(Fun), is_list(Args) -> ensure_ok_or_exit(Fun, Args = [Sock | _]) when is_atom(Fun), is_list(Args) ->
case erlang:apply(?MODULE, Fun, Args) of case erlang:apply(?MODULE, Fun, Args) of
{error, Reason} when Reason =:= enotconn; Reason =:= closed -> {error, Reason} when Reason =:= enotconn; Reason =:= closed ->
fast_close(Sock), fast_close(Sock),
@ -91,7 +98,8 @@ ensure_ok_or_exit(Fun, Args = [Sock|_]) when is_atom(Fun), is_list(Args) ->
{error, Reason} -> {error, Reason} ->
fast_close(Sock), fast_close(Sock),
exit({shutdown, Reason}); exit({shutdown, Reason});
Result -> Result Result ->
Result
end. end.
async_send(Stream, Data, Options) when is_list(Data) -> async_send(Stream, Data, Options) when is_list(Data) ->
@ -99,6 +107,5 @@ async_send(Stream, Data, Options) when is_list(Data) ->
async_send(Stream, Data, _Options) when is_binary(Data) -> async_send(Stream, Data, _Options) when is_binary(Data) ->
case quicer:send(Stream, Data) of case quicer:send(Stream, Data) of
{ok, _Len} -> ok; {ok, _Len} -> ok;
Other -> Other -> Other
Other
end. end.

View File

@ -19,27 +19,36 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-export([ name/1 -export([
, name/2 name/1,
, text/1 name/2,
, text/2 text/1,
]). text/2
]).
-export([ frame_error/1 -export([
, connack_error/1 frame_error/1,
]). connack_error/1
]).
-export([compat/2]). -export([compat/2]).
name(I, Ver) when Ver >= ?MQTT_PROTO_V5 -> name(I, Ver) when Ver >= ?MQTT_PROTO_V5 ->
name(I); name(I);
name(0, _Ver) -> connection_accepted; name(0, _Ver) ->
name(1, _Ver) -> unacceptable_protocol_version; connection_accepted;
name(2, _Ver) -> client_identifier_not_valid; name(1, _Ver) ->
name(3, _Ver) -> server_unavaliable; unacceptable_protocol_version;
name(4, _Ver) -> malformed_username_or_password; name(2, _Ver) ->
name(5, _Ver) -> unauthorized_client; client_identifier_not_valid;
name(_, _Ver) -> unknown_error. name(3, _Ver) ->
server_unavaliable;
name(4, _Ver) ->
malformed_username_or_password;
name(5, _Ver) ->
unauthorized_client;
name(_, _Ver) ->
unknown_error.
name(16#00) -> success; name(16#00) -> success;
name(16#01) -> granted_qos1; name(16#01) -> granted_qos1;
@ -88,13 +97,20 @@ name(_Code) -> unknown_error.
text(I, Ver) when Ver >= ?MQTT_PROTO_V5 -> text(I, Ver) when Ver >= ?MQTT_PROTO_V5 ->
text(I); text(I);
text(0, _Ver) -> <<"Connection accepted">>; text(0, _Ver) ->
text(1, _Ver) -> <<"unacceptable_protocol_version">>; <<"Connection accepted">>;
text(2, _Ver) -> <<"client_identifier_not_valid">>; text(1, _Ver) ->
text(3, _Ver) -> <<"server_unavaliable">>; <<"unacceptable_protocol_version">>;
text(4, _Ver) -> <<"malformed_username_or_password">>; text(2, _Ver) ->
text(5, _Ver) -> <<"unauthorized_client">>; <<"client_identifier_not_valid">>;
text(_, _Ver) -> <<"unknown_error">>. text(3, _Ver) ->
<<"server_unavaliable">>;
text(4, _Ver) ->
<<"malformed_username_or_password">>;
text(5, _Ver) ->
<<"unauthorized_client">>;
text(_, _Ver) ->
<<"unknown_error">>.
text(16#00) -> <<"Success">>; text(16#00) -> <<"Success">>;
text(16#01) -> <<"Granted QoS 1">>; text(16#01) -> <<"Granted QoS 1">>;
@ -159,10 +175,8 @@ compat(connack, 16#97) -> ?CONNACK_SERVER;
compat(connack, 16#9C) -> ?CONNACK_SERVER; compat(connack, 16#9C) -> ?CONNACK_SERVER;
compat(connack, 16#9D) -> ?CONNACK_SERVER; compat(connack, 16#9D) -> ?CONNACK_SERVER;
compat(connack, 16#9F) -> ?CONNACK_SERVER; compat(connack, 16#9F) -> ?CONNACK_SERVER;
compat(suback, Code) when Code =< ?QOS_2 -> Code; compat(suback, Code) when Code =< ?QOS_2 -> Code;
compat(suback, Code) when Code >= 16#80 -> 16#80; compat(suback, Code) when Code >= 16#80 -> 16#80;
compat(unsuback, _Code) -> undefined; compat(unsuback, _Code) -> undefined;
compat(_Other, _Code) -> undefined. compat(_Other, _Code) -> undefined.
@ -177,4 +191,3 @@ connack_error(server_busy) -> ?RC_SERVER_BUSY;
connack_error(banned) -> ?RC_BANNED; connack_error(banned) -> ?RC_BANNED;
connack_error(bad_authentication_method) -> ?RC_BAD_AUTHENTICATION_METHOD; connack_error(bad_authentication_method) -> ?RC_BAD_AUTHENTICATION_METHOD;
connack_error(_) -> ?RC_UNSPECIFIED_ERROR. connack_error(_) -> ?RC_UNSPECIFIED_ERROR.

View File

@ -16,24 +16,25 @@
-module(emqx_release). -module(emqx_release).
-export([ edition/0 -export([
, description/0 edition/0,
, version/0 description/0,
]). version/0
]).
-include("emqx_release.hrl"). -include("emqx_release.hrl").
-define(EMQX_DESCS, -define(EMQX_DESCS, #{
#{ee => "EMQX Enterprise", ee => "EMQX Enterprise",
ce => "EMQX", ce => "EMQX",
edge => "EMQX Edge" edge => "EMQX Edge"
}). }).
-define(EMQX_REL_VSNS, -define(EMQX_REL_VSNS, #{
#{ee => ?EMQX_RELEASE_EE, ee => ?EMQX_RELEASE_EE,
ce => ?EMQX_RELEASE_CE, ce => ?EMQX_RELEASE_CE,
edge => ?EMQX_RELEASE_CE edge => ?EMQX_RELEASE_CE
}). }).
%% @doc Return EMQX description. %% @doc Return EMQX description.
description() -> description() ->
@ -52,17 +53,21 @@ edition() -> ce.
%% @doc Return the release version. %% @doc Return the release version.
version() -> version() ->
case lists:keyfind(emqx_vsn, 1, ?MODULE:module_info(compile)) of case lists:keyfind(emqx_vsn, 1, ?MODULE:module_info(compile)) of
false -> %% For TEST build or dependency build. %% For TEST build or dependency build.
false ->
build_vsn(); build_vsn();
{_, Vsn} -> %% For emqx release build %% For emqx release build
{_, Vsn} ->
VsnStr = build_vsn(), VsnStr = build_vsn(),
case string:str(Vsn, VsnStr) of case string:str(Vsn, VsnStr) of
1 -> ok; 1 ->
ok;
_ -> _ ->
erlang:error(#{ reason => version_mismatch erlang:error(#{
, source => VsnStr reason => version_mismatch,
, built_for => Vsn source => VsnStr,
}) built_for => Vsn
})
end, end,
Vsn Vsn
end. end.

View File

@ -23,7 +23,6 @@
-include("types.hrl"). -include("types.hrl").
-include_lib("ekka/include/ekka.hrl"). -include_lib("ekka/include/ekka.hrl").
%% Mnesia bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
@ -32,39 +31,43 @@
-export([start_link/2]). -export([start_link/2]).
%% Route APIs %% Route APIs
-export([ add_route/1 -export([
, add_route/2 add_route/1,
, do_add_route/1 add_route/2,
, do_add_route/2 do_add_route/1,
]). do_add_route/2
]).
-export([ delete_route/1 -export([
, delete_route/2 delete_route/1,
, do_delete_route/1 delete_route/2,
, do_delete_route/2 do_delete_route/1,
]). do_delete_route/2
]).
-export([ match_routes/1 -export([
, lookup_routes/1 match_routes/1,
, has_routes/1 lookup_routes/1,
]). has_routes/1
]).
-export([print_routes/1]). -export([print_routes/1]).
-export([topics/0]). -export([topics/0]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-type(group() :: binary()). -type group() :: binary().
-type(dest() :: node() | {group(), node()}). -type dest() :: node() | {group(), node()}.
-define(ROUTE_TAB, emqx_route). -define(ROUTE_TAB, emqx_route).
@ -75,48 +78,58 @@
mnesia(boot) -> mnesia(boot) ->
mria_config:set_dirty_shard(?ROUTE_SHARD, true), mria_config:set_dirty_shard(?ROUTE_SHARD, true),
ok = mria:create_table(?ROUTE_TAB, [ ok = mria:create_table(?ROUTE_TAB, [
{type, bag}, {type, bag},
{rlog_shard, ?ROUTE_SHARD}, {rlog_shard, ?ROUTE_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, route}, {record_name, route},
{attributes, record_info(fields, route)}, {attributes, record_info(fields, route)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]). {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Start a router %% Start a router
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link(atom(), pos_integer()) -> startlink_ret()). -spec start_link(atom(), pos_integer()) -> startlink_ret().
start_link(Pool, Id) -> start_link(Pool, Id) ->
gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, gen_server:start_link(
?MODULE, [Pool, Id], [{hibernate_after, 1000}]). {local, emqx_misc:proc_name(?MODULE, Id)},
?MODULE,
[Pool, Id],
[{hibernate_after, 1000}]
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Route APIs %% Route APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(add_route(emqx_types:topic()) -> ok | {error, term()}). -spec add_route(emqx_types:topic()) -> ok | {error, term()}.
add_route(Topic) when is_binary(Topic) -> add_route(Topic) when is_binary(Topic) ->
add_route(Topic, node()). add_route(Topic, node()).
-spec(add_route(emqx_types:topic(), dest()) -> ok | {error, term()}). -spec add_route(emqx_types:topic(), dest()) -> ok | {error, term()}.
add_route(Topic, Dest) when is_binary(Topic) -> add_route(Topic, Dest) when is_binary(Topic) ->
call(pick(Topic), {add_route, Topic, Dest}). call(pick(Topic), {add_route, Topic, Dest}).
-spec(do_add_route(emqx_types:topic()) -> ok | {error, term()}). -spec do_add_route(emqx_types:topic()) -> ok | {error, term()}.
do_add_route(Topic) when is_binary(Topic) -> do_add_route(Topic) when is_binary(Topic) ->
do_add_route(Topic, node()). do_add_route(Topic, node()).
-spec(do_add_route(emqx_types:topic(), dest()) -> ok | {error, term()}). -spec do_add_route(emqx_types:topic(), dest()) -> ok | {error, term()}.
do_add_route(Topic, Dest) when is_binary(Topic) -> do_add_route(Topic, Dest) when is_binary(Topic) ->
Route = #route{topic = Topic, dest = Dest}, Route = #route{topic = Topic, dest = Dest},
case lists:member(Route, lookup_routes(Topic)) of case lists:member(Route, lookup_routes(Topic)) of
true -> ok; true ->
ok;
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 -> true ->
Fun = fun emqx_router_utils:insert_trie_route/2, Fun = fun emqx_router_utils:insert_trie_route/2,
emqx_router_utils:maybe_trans(Fun, [?ROUTE_TAB, Route], ?ROUTE_SHARD); emqx_router_utils:maybe_trans(Fun, [?ROUTE_TAB, Route], ?ROUTE_SHARD);
false -> false ->
@ -125,12 +138,11 @@ do_add_route(Topic, Dest) when is_binary(Topic) ->
end. end.
%% @doc Match routes %% @doc Match routes
-spec(match_routes(emqx_types:topic()) -> [emqx_types:route()]). -spec match_routes(emqx_types:topic()) -> [emqx_types:route()].
match_routes(Topic) when is_binary(Topic) -> match_routes(Topic) when is_binary(Topic) ->
case match_trie(Topic) of case match_trie(Topic) of
[] -> lookup_routes(Topic); [] -> lookup_routes(Topic);
Matched -> Matched -> lists:append([lookup_routes(To) || To <- [Topic | Matched]])
lists:append([lookup_routes(To) || To <- [Topic | Matched]])
end. end.
%% Optimize: routing table will be replicated to all router nodes. %% Optimize: routing table will be replicated to all router nodes.
@ -140,47 +152,50 @@ match_trie(Topic) ->
false -> emqx_trie:match(Topic) false -> emqx_trie:match(Topic)
end. end.
-spec(lookup_routes(emqx_types:topic()) -> [emqx_types:route()]). -spec lookup_routes(emqx_types:topic()) -> [emqx_types:route()].
lookup_routes(Topic) -> lookup_routes(Topic) ->
ets:lookup(?ROUTE_TAB, Topic). ets:lookup(?ROUTE_TAB, Topic).
-spec(has_routes(emqx_types:topic()) -> boolean()). -spec has_routes(emqx_types:topic()) -> boolean().
has_routes(Topic) when is_binary(Topic) -> has_routes(Topic) when is_binary(Topic) ->
ets:member(?ROUTE_TAB, Topic). ets:member(?ROUTE_TAB, Topic).
-spec(delete_route(emqx_types:topic()) -> ok | {error, term()}). -spec delete_route(emqx_types:topic()) -> ok | {error, term()}.
delete_route(Topic) when is_binary(Topic) -> delete_route(Topic) when is_binary(Topic) ->
delete_route(Topic, node()). delete_route(Topic, node()).
-spec(delete_route(emqx_types:topic(), dest()) -> ok | {error, term()}). -spec delete_route(emqx_types:topic(), dest()) -> ok | {error, term()}.
delete_route(Topic, Dest) when is_binary(Topic) -> delete_route(Topic, Dest) when is_binary(Topic) ->
call(pick(Topic), {delete_route, Topic, Dest}). call(pick(Topic), {delete_route, Topic, Dest}).
-spec(do_delete_route(emqx_types:topic()) -> ok | {error, term()}). -spec do_delete_route(emqx_types:topic()) -> ok | {error, term()}.
do_delete_route(Topic) when is_binary(Topic) -> do_delete_route(Topic) when is_binary(Topic) ->
do_delete_route(Topic, node()). do_delete_route(Topic, node()).
-spec(do_delete_route(emqx_types:topic(), dest()) -> ok | {error, term()}). -spec do_delete_route(emqx_types:topic(), dest()) -> ok | {error, term()}.
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 -> true ->
Fun = fun emqx_router_utils:delete_trie_route/2, Fun = fun emqx_router_utils:delete_trie_route/2,
emqx_router_utils:maybe_trans(Fun, [?ROUTE_TAB, Route], ?ROUTE_SHARD); emqx_router_utils:maybe_trans(Fun, [?ROUTE_TAB, Route], ?ROUTE_SHARD);
false -> false ->
emqx_router_utils:delete_direct_route(?ROUTE_TAB, Route) emqx_router_utils:delete_direct_route(?ROUTE_TAB, Route)
end. end.
-spec(topics() -> list(emqx_types:topic())). -spec topics() -> list(emqx_types:topic()).
topics() -> topics() ->
mnesia:dirty_all_keys(?ROUTE_TAB). mnesia:dirty_all_keys(?ROUTE_TAB).
%% @doc Print routes to a topic %% @doc Print routes to a topic
-spec(print_routes(emqx_types:topic()) -> ok). -spec print_routes(emqx_types:topic()) -> ok.
print_routes(Topic) -> print_routes(Topic) ->
lists:foreach(fun(#route{topic = To, dest = Dest}) -> lists:foreach(
io:format("~ts -> ~ts~n", [To, Dest]) fun(#route{topic = To, dest = Dest}) ->
end, match_routes(Topic)). io:format("~ts -> ~ts~n", [To, Dest])
end,
match_routes(Topic)
).
call(Router, Msg) -> call(Router, Msg) ->
gen_server:call(Router, Msg, infinity). gen_server:call(Router, Msg, infinity).
@ -199,11 +214,9 @@ init([Pool, Id]) ->
handle_call({add_route, Topic, Dest}, _From, State) -> handle_call({add_route, Topic, Dest}, _From, State) ->
Ok = do_add_route(Topic, Dest), Ok = do_add_route(Topic, Dest),
{reply, Ok, State}; {reply, Ok, State};
handle_call({delete_route, Topic, Dest}, _From, State) -> handle_call({delete_route, Topic, Dest}, _From, State) ->
Ok = do_delete_route(Topic, Dest), Ok = do_delete_route(Topic, Dest),
{reply, Ok, State}; {reply, Ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.

View File

@ -23,28 +23,29 @@
-include("types.hrl"). -include("types.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
%% Mnesia bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
%% API %% API
-export([ start_link/0 -export([
, monitor/1 start_link/0,
]). monitor/1
]).
%% Internal export %% Internal export
-export([stats_fun/0]). -export([stats_fun/0]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-record(routing_node, {name, const = unused}). -record(routing_node, {name, const = unused}).
@ -60,30 +61,33 @@
mnesia(boot) -> mnesia(boot) ->
ok = mria:create_table(?ROUTING_NODE, [ ok = mria:create_table(?ROUTING_NODE, [
{type, set}, {type, set},
{rlog_shard, ?ROUTE_SHARD}, {rlog_shard, ?ROUTE_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, routing_node}, {record_name, routing_node},
{attributes, record_info(fields, routing_node)}, {attributes, record_info(fields, routing_node)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}]). {storage_properties, [{ets, [{read_concurrency, true}]}]}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Starts the router helper %% @doc Starts the router helper
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% @doc Monitor routing node %% @doc Monitor routing node
-spec(monitor(node() | {binary(), node()}) -> ok). -spec monitor(node() | {binary(), node()}) -> ok.
monitor({_Group, Node}) -> monitor({_Group, Node}) ->
monitor(Node); monitor(Node);
monitor(Node) when is_atom(Node) -> monitor(Node) when is_atom(Node) ->
case ekka:is_member(Node) case
orelse ets:member(?ROUTING_NODE, Node) of ekka:is_member(Node) orelse
true -> ok; ets:member(?ROUTING_NODE, Node)
of
true -> ok;
false -> mria:dirty_write(?ROUTING_NODE, #routing_node{name = Node}) false -> mria:dirty_write(?ROUTING_NODE, #routing_node{name = Node})
end. end.
@ -97,13 +101,18 @@ init([]) ->
_ = mria:wait_for_tables([?ROUTING_NODE]), _ = mria:wait_for_tables([?ROUTING_NODE]),
{ok, _} = mnesia:subscribe({table, ?ROUTING_NODE, simple}), {ok, _} = mnesia:subscribe({table, ?ROUTING_NODE, simple}),
Nodes = lists:foldl( Nodes = lists:foldl(
fun(Node, Acc) -> fun(Node, Acc) ->
case ekka:is_member(Node) of case ekka:is_member(Node) of
true -> Acc; true ->
false -> true = erlang:monitor_node(Node, true), Acc;
[Node | Acc] false ->
end true = erlang:monitor_node(Node, true),
end, [], mnesia:dirty_all_keys(?ROUTING_NODE)), [Node | Acc]
end
end,
[],
mnesia:dirty_all_keys(?ROUTING_NODE)
),
ok = emqx_stats:update_interval(route_stats, fun ?MODULE:stats_fun/0), ok = emqx_stats:update_interval(route_stats, fun ?MODULE:stats_fun/0),
{ok, #{nodes => Nodes}, hibernate}. {ok, #{nodes => Nodes}, hibernate}.
@ -115,41 +124,39 @@ handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
handle_info({mnesia_table_event, {write, {?ROUTING_NODE, Node, _}, _}}, handle_info(
State = #{nodes := Nodes}) -> {mnesia_table_event, {write, {?ROUTING_NODE, Node, _}, _}},
State = #{nodes := Nodes}
) ->
case ekka:is_member(Node) orelse lists:member(Node, Nodes) of case ekka:is_member(Node) orelse lists:member(Node, Nodes) of
true -> {noreply, State}; true ->
{noreply, State};
false -> false ->
true = erlang:monitor_node(Node, true), true = erlang:monitor_node(Node, true),
{noreply, State#{nodes := [Node | Nodes]}} {noreply, State#{nodes := [Node | Nodes]}}
end; end;
handle_info({mnesia_table_event, {delete, {?ROUTING_NODE, _Node}, _}}, State) -> handle_info({mnesia_table_event, {delete, {?ROUTING_NODE, _Node}, _}}, State) ->
%% ignore %% ignore
{noreply, State}; {noreply, State};
handle_info({mnesia_table_event, Event}, State) -> handle_info({mnesia_table_event, Event}, State) ->
?SLOG(error,#{msg => "unexpected_mnesia_table_event", event => Event}), ?SLOG(error, #{msg => "unexpected_mnesia_table_event", event => Event}),
{noreply, State}; {noreply, State};
handle_info({nodedown, Node}, State = #{nodes := Nodes}) -> handle_info({nodedown, Node}, State = #{nodes := Nodes}) ->
global:trans({?LOCK, self()}, global:trans(
fun() -> {?LOCK, self()},
mria:transaction(?ROUTE_SHARD, fun cleanup_routes/1, [Node]) fun() ->
end), mria:transaction(?ROUTE_SHARD, fun cleanup_routes/1, [Node])
end
),
ok = mria:dirty_delete(?ROUTING_NODE, Node), ok = mria:dirty_delete(?ROUTING_NODE, Node),
?tp(emqx_router_helper_cleanup_done, #{node => Node}), ?tp(emqx_router_helper_cleanup_done, #{node => Node}),
{noreply, State#{nodes := lists:delete(Node, Nodes)}, hibernate}; {noreply, State#{nodes := lists:delete(Node, Nodes)}, hibernate};
handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({nodedown, Node}, State); handle_info({nodedown, Node}, State);
handle_info({membership, {node, down, Node}}, State) -> handle_info({membership, {node, down, Node}}, State) ->
handle_info({nodedown, Node}, State); handle_info({nodedown, Node}, State);
handle_info({membership, _Event}, State) -> handle_info({membership, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -168,14 +175,19 @@ code_change(_OldVsn, State, _Extra) ->
stats_fun() -> stats_fun() ->
case ets:info(?ROUTE, size) of case ets:info(?ROUTE, size) of
undefined -> ok; undefined ->
ok;
Size -> Size ->
emqx_stats:setstat('routes.count', 'routes.max', Size), emqx_stats:setstat('routes.count', 'routes.max', Size),
emqx_stats:setstat('topics.count', 'topics.max', Size) emqx_stats:setstat('topics.count', 'topics.max', Size)
end. end.
cleanup_routes(Node) -> cleanup_routes(Node) ->
Patterns = [#route{_ = '_', dest = Node}, Patterns = [
#route{_ = '_', dest = {'_', Node}}], #route{_ = '_', dest = Node},
[mnesia:delete_object(?ROUTE, Route, write) #route{_ = '_', dest = {'_', Node}}
|| Pat <- Patterns, Route <- mnesia:match_object(?ROUTE, Pat, write)]. ],
[
mnesia:delete_object(?ROUTE, Route, write)
|| Pat <- Patterns, Route <- mnesia:match_object(?ROUTE, Pat, write)
].

View File

@ -27,14 +27,18 @@ start_link() ->
init([]) -> init([]) ->
%% Router helper %% Router helper
Helper = #{id => helper, Helper = #{
start => {emqx_router_helper, start_link, []}, id => helper,
restart => permanent, start => {emqx_router_helper, start_link, []},
shutdown => 5000, restart => permanent,
type => worker, shutdown => 5000,
modules => [emqx_router_helper]}, type => worker,
modules => [emqx_router_helper]
},
%% Router pool %% Router pool
RouterPool = emqx_pool_sup:spec([router_pool, hash, RouterPool = emqx_pool_sup:spec([
{emqx_router, start_link, []}]), router_pool,
hash,
{emqx_router, start_link, []}
]),
{ok, {{one_for_all, 0, 1}, [Helper, RouterPool]}}. {ok, {{one_for_all, 0, 1}, [Helper, RouterPool]}}.

View File

@ -18,14 +18,15 @@
-include("emqx.hrl"). -include("emqx.hrl").
-export([ delete_direct_route/2 -export([
, delete_trie_route/2 delete_direct_route/2,
, delete_session_trie_route/2 delete_trie_route/2,
, insert_direct_route/2 delete_session_trie_route/2,
, insert_trie_route/2 insert_direct_route/2,
, insert_session_trie_route/2 insert_trie_route/2,
, maybe_trans/3 insert_session_trie_route/2,
]). maybe_trans/3
]).
insert_direct_route(Tab, Route) -> insert_direct_route(Tab, Route) ->
mria:dirty_write(Tab, Route). mria:dirty_write(Tab, Route).
@ -33,14 +34,14 @@ insert_direct_route(Tab, Route) ->
insert_trie_route(RouteTab, Route = #route{topic = Topic}) -> insert_trie_route(RouteTab, Route = #route{topic = Topic}) ->
case mnesia:wread({RouteTab, Topic}) of case mnesia:wread({RouteTab, Topic}) of
[] -> emqx_trie:insert(Topic); [] -> emqx_trie:insert(Topic);
_ -> ok _ -> ok
end, end,
mnesia:write(RouteTab, Route, sticky_write). mnesia:write(RouteTab, Route, sticky_write).
insert_session_trie_route(RouteTab, Route = #route{topic = Topic}) -> insert_session_trie_route(RouteTab, Route = #route{topic = Topic}) ->
case mnesia:wread({RouteTab, Topic}) of case mnesia:wread({RouteTab, Topic}) of
[] -> emqx_trie:insert_session(Topic); [] -> emqx_trie:insert_session(Topic);
_ -> ok _ -> ok
end, end,
mnesia:write(RouteTab, Route, sticky_write). mnesia:write(RouteTab, Route, sticky_write).
@ -59,10 +60,10 @@ delete_trie_route(RouteTab, Route = #route{topic = Topic}, Type) ->
%% Remove route and trie %% Remove route and trie
ok = mnesia:delete_object(RouteTab, Route, sticky_write), ok = mnesia:delete_object(RouteTab, Route, sticky_write),
case Type of case Type of
normal -> emqx_trie:delete(Topic); normal -> emqx_trie:delete(Topic);
session -> emqx_trie:delete_session(Topic) session -> emqx_trie:delete_session(Topic)
end; end;
[_|_] -> [_ | _] ->
%% Remove route only %% Remove route only
mnesia:delete_object(RouteTab, Route, sticky_write); mnesia:delete_object(RouteTab, Route, sticky_write);
[] -> [] ->
@ -70,30 +71,37 @@ delete_trie_route(RouteTab, Route = #route{topic = Topic}, Type) ->
end. end.
%% @private %% @private
-spec(maybe_trans(function(), list(any()), Shard :: atom()) -> ok | {error, term()}). -spec maybe_trans(function(), list(any()), Shard :: atom()) -> ok | {error, term()}.
maybe_trans(Fun, Args, Shard) -> maybe_trans(Fun, Args, Shard) ->
case emqx:get_config([broker, perf, route_lock_type]) of case emqx:get_config([broker, perf, route_lock_type]) of
key -> key ->
trans(Fun, Args, Shard); trans(Fun, Args, Shard);
global -> global ->
%% Assert: %% Assert:
mnesia = mria_rlog:backend(), %% TODO: do something smarter than just crash
%% TODO: do something smarter than just crash
mnesia = mria_rlog:backend(),
lock_router(Shard), lock_router(Shard),
try mnesia:sync_dirty(Fun, Args) try
mnesia:sync_dirty(Fun, Args)
after after
unlock_router(Shard) unlock_router(Shard)
end; end;
tab -> tab ->
trans(fun() -> trans(
emqx_trie:lock_tables(), fun() ->
apply(Fun, Args) emqx_trie:lock_tables(),
end, [], Shard) apply(Fun, Args)
end,
[],
Shard
)
end. end.
%% The created fun only terminates with explicit exception %% The created fun only terminates with explicit exception
-dialyzer({nowarn_function, [trans/3]}). -dialyzer({nowarn_function, [trans/3]}).
-spec(trans(function(), list(any()), atom()) -> ok | {error, term()}). -spec trans(function(), list(any()), atom()) -> ok | {error, term()}.
trans(Fun, Args, Shard) -> trans(Fun, Args, Shard) ->
{WPid, RefMon} = {WPid, RefMon} =
spawn_monitor( spawn_monitor(
@ -102,12 +110,14 @@ trans(Fun, Args, Shard) ->
%% Future changes should keep in mind that this process %% Future changes should keep in mind that this process
%% always exit with database write result. %% always exit with database write result.
fun() -> fun() ->
Res = case mria:transaction(Shard, Fun, Args) of Res =
{atomic, Ok} -> Ok; case mria:transaction(Shard, Fun, Args) of
{aborted, Reason} -> {error, Reason} {atomic, Ok} -> Ok;
end, {aborted, Reason} -> {error, Reason}
exit({shutdown, Res}) end,
end), exit({shutdown, Res})
end
),
%% Receive a 'shutdown' exit to pass result from the short-lived process. %% Receive a 'shutdown' exit to pass result from the short-lived process.
%% so the receive below can be receive-mark optimized by the compiler. %% so the receive below can be receive-mark optimized by the compiler.
%% %%

View File

@ -19,33 +19,37 @@
%% Note: please don't forget to add new API functions to %% Note: please don't forget to add new API functions to
%% `emqx_bpapi_trans:extract_mfa' %% `emqx_bpapi_trans:extract_mfa'
-export([ call/4 -export([
, call/5 call/4,
, cast/4 call/5,
, cast/5 cast/4,
, multicall/4 cast/5,
, multicall/5 multicall/4,
multicall/5,
, unwrap_erpc/1 unwrap_erpc/1
]). ]).
-export_type([ badrpc/0 -export_type([
, call_result/0 badrpc/0,
, cast_result/0 call_result/0,
, multicall_result/1 cast_result/0,
, multicall_result/0 multicall_result/1,
, erpc/1 multicall_result/0,
, erpc_multicall/1 erpc/1,
]). erpc_multicall/1
]).
-compile({inline, -compile(
[ rpc_node/1 {inline, [
, rpc_nodes/1 rpc_node/1,
]}). rpc_nodes/1
]}
).
-define(DefaultClientNum, 1). -define(DefaultClientNum, 1).
-type badrpc() :: {badrpc, term()} | {badtcp, term()}. -type badrpc() :: {badrpc, term()} | {badtcp, term()}.
-type call_result() :: term() | badrpc(). -type call_result() :: term() | badrpc().
@ -55,11 +59,12 @@
-type multicall_result() :: multicall_result(term()). -type multicall_result() :: multicall_result(term()).
-type erpc(Ret) :: {ok, Ret} -type erpc(Ret) ::
| {throw, _Err} {ok, Ret}
| {exit, {exception | signal, _Reason}} | {throw, _Err}
| {error, {exception, _Reason, _Stack :: list()}} | {exit, {exception | signal, _Reason}}
| {error, {erpc, _Reason}}. | {error, {exception, _Reason, _Stack :: list()}}
| {error, {erpc, _Reason}}.
-type erpc_multicall(Ret) :: [erpc(Ret)]. -type erpc_multicall(Ret) :: [erpc(Ret)].
@ -100,8 +105,9 @@ rpc_nodes([], Acc) ->
rpc_nodes([Node | Nodes], Acc) -> rpc_nodes([Node | Nodes], Acc) ->
rpc_nodes(Nodes, [rpc_node(Node) | Acc]). rpc_nodes(Nodes, [rpc_node(Node) | Acc]).
filter_result({Error, Reason}) filter_result({Error, Reason}) when
when Error =:= badrpc; Error =:= badtcp -> Error =:= badrpc; Error =:= badtcp
->
{badrpc, Reason}; {badrpc, Reason};
filter_result(Delivery) -> filter_result(Delivery) ->
Delivery. Delivery.

File diff suppressed because it is too large Load Diff

View File

@ -16,58 +16,62 @@
-module(emqx_sequence). -module(emqx_sequence).
-export([ create/1 -export([
, nextval/2 create/1,
, currval/2 nextval/2,
, reclaim/2 currval/2,
, delete/1 reclaim/2,
]). delete/1
]).
-export_type([seqid/0]). -export_type([seqid/0]).
-type(key() :: term()). -type key() :: term().
-type(name() :: atom()). -type name() :: atom().
-type(seqid() :: non_neg_integer()). -type seqid() :: non_neg_integer().
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Create a sequence. %% @doc Create a sequence.
-spec(create(name()) -> ok). -spec create(name()) -> ok.
create(Name) -> create(Name) ->
emqx_tables:new(Name, [public, set, {write_concurrency, true}]). emqx_tables:new(Name, [public, set, {write_concurrency, true}]).
%% @doc Next value of the sequence. %% @doc Next value of the sequence.
-spec(nextval(name(), key()) -> seqid()). -spec nextval(name(), key()) -> seqid().
nextval(Name, Key) -> nextval(Name, Key) ->
ets:update_counter(Name, Key, {2, 1}, {Key, 0}). ets:update_counter(Name, Key, {2, 1}, {Key, 0}).
%% @doc Current value of the sequence. %% @doc Current value of the sequence.
-spec(currval(name(), key()) -> seqid()). -spec currval(name(), key()) -> seqid().
currval(Name, Key) -> currval(Name, Key) ->
try ets:lookup_element(Name, Key, 2) try
ets:lookup_element(Name, Key, 2)
catch catch
error:badarg -> 0 error:badarg -> 0
end. end.
%% @doc Reclaim a sequence id. %% @doc Reclaim a sequence id.
-spec(reclaim(name(), key()) -> seqid()). -spec reclaim(name(), key()) -> seqid().
reclaim(Name, Key) -> reclaim(Name, Key) ->
try ets:update_counter(Name, Key, {2, -1, 0, 0}) of try ets:update_counter(Name, Key, {2, -1, 0, 0}) of
0 -> ets:delete_object(Name, {Key, 0}), 0; 0 ->
I -> I ets:delete_object(Name, {Key, 0}),
0;
I ->
I
catch catch
error:badarg -> 0 error:badarg -> 0
end. end.
%% @doc Delete the sequence. %% @doc Delete the sequence.
-spec(delete(name()) -> boolean()). -spec delete(name()) -> boolean().
delete(Name) -> delete(Name) ->
case ets:info(Name, name) of case ets:info(Name, name) of
Name -> ets:delete(Name); Name -> ets:delete(Name);
undefined -> false undefined -> false
end. end.

View File

@ -48,7 +48,6 @@
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
@ -56,36 +55,41 @@
-export([init/1]). -export([init/1]).
-export([ info/1 -export([
, info/2 info/1,
, is_session/1 info/2,
, stats/1 is_session/1,
, obtain_next_pkt_id/1 stats/1,
]). obtain_next_pkt_id/1
]).
-export([ subscribe/4 -export([
, unsubscribe/4 subscribe/4,
]). unsubscribe/4
]).
-export([ publish/4 -export([
, puback/3 publish/4,
, pubrec/3 puback/3,
, pubrel/3 pubrec/3,
, pubcomp/3 pubrel/3,
]). pubcomp/3
]).
-export([ deliver/3 -export([
, enqueue/3 deliver/3,
, dequeue/2 enqueue/3,
, ignore_local/4 dequeue/2,
, retry/2 ignore_local/4,
, terminate/3 retry/2,
]). terminate/3
]).
-export([ takeover/1 -export([
, resume/2 takeover/1,
, replay/2 resume/2,
]). replay/2
]).
-export([expire/3]). -export([expire/3]).
@ -94,124 +98,130 @@
-type sessionID() :: emqx_guid:guid(). -type sessionID() :: emqx_guid:guid().
-export_type([ session/0 -export_type([
, sessionID/0 session/0,
]). sessionID/0
]).
-record(session, { -record(session, {
%% Client's id %% Client's id
clientid :: emqx_types:clientid(), clientid :: emqx_types:clientid(),
id :: sessionID(), id :: sessionID(),
%% Is this session a persistent session i.e. was it started with Session-Expiry > 0 %% Is this session a persistent session i.e. was it started with Session-Expiry > 0
is_persistent :: boolean(), is_persistent :: boolean(),
%% Clients Subscriptions. %% Clients Subscriptions.
subscriptions :: map(), subscriptions :: map(),
%% Max subscriptions allowed %% Max subscriptions allowed
max_subscriptions :: non_neg_integer() | infinity, max_subscriptions :: non_neg_integer() | infinity,
%% Upgrade QoS? %% Upgrade QoS?
upgrade_qos :: boolean(), upgrade_qos :: boolean(),
%% Client <- Broker: QoS1/2 messages sent to the client but %% Client <- Broker: QoS1/2 messages sent to the client but
%% have not been unacked. %% have not been unacked.
inflight :: emqx_inflight:inflight(), inflight :: emqx_inflight:inflight(),
%% All QoS1/2 messages published to when client is disconnected, %% All QoS1/2 messages published to when client is disconnected,
%% or QoS1/2 messages pending transmission to the Client. %% or QoS1/2 messages pending transmission to the Client.
%% %%
%% Optionally, QoS0 messages pending transmission to the Client. %% Optionally, QoS0 messages pending transmission to the Client.
mqueue :: emqx_mqueue:mqueue(), mqueue :: emqx_mqueue:mqueue(),
%% Next packet id of the session %% Next packet id of the session
next_pkt_id = 1 :: emqx_types:packet_id(), next_pkt_id = 1 :: emqx_types:packet_id(),
%% Retry interval for redelivering QoS1/2 messages (Unit: millisecond) %% Retry interval for redelivering QoS1/2 messages (Unit: millisecond)
retry_interval :: timeout(), retry_interval :: timeout(),
%% Client -> Broker: QoS2 messages received from the client, but %% Client -> Broker: QoS2 messages received from the client, but
%% have not been completely acknowledged %% have not been completely acknowledged
awaiting_rel :: map(), awaiting_rel :: map(),
%% Maximum number of awaiting QoS2 messages allowed %% Maximum number of awaiting QoS2 messages allowed
max_awaiting_rel :: non_neg_integer() | infinity, max_awaiting_rel :: non_neg_integer() | infinity,
%% Awaiting PUBREL Timeout (Unit: millisecond) %% Awaiting PUBREL Timeout (Unit: millisecond)
await_rel_timeout :: timeout(), await_rel_timeout :: timeout(),
%% Created at %% Created at
created_at :: pos_integer() created_at :: pos_integer()
%% Message deliver latency stats %% Message deliver latency stats
}). }).
-type inflight_data_phase() :: wait_ack | wait_comp. -type inflight_data_phase() :: wait_ack | wait_comp.
-record(inflight_data, { phase :: inflight_data_phase() -record(inflight_data, {
, message :: emqx_types:message() phase :: inflight_data_phase(),
, timestamp :: non_neg_integer()}). message :: emqx_types:message(),
timestamp :: non_neg_integer()
}).
-type(session() :: #session{}). -type session() :: #session{}.
-type(publish() :: {maybe(emqx_types:packet_id()), emqx_types:message()}). -type publish() :: {maybe(emqx_types:packet_id()), emqx_types:message()}.
-type(pubrel() :: {pubrel, emqx_types:packet_id()}). -type pubrel() :: {pubrel, emqx_types:packet_id()}.
-type(replies() :: list(publish() | pubrel())). -type replies() :: list(publish() | pubrel()).
-define(INFO_KEYS, -define(INFO_KEYS, [
[ id id,
, is_persistent is_persistent,
, subscriptions subscriptions,
, upgrade_qos upgrade_qos,
, retry_interval retry_interval,
, await_rel_timeout await_rel_timeout,
, created_at created_at
]). ]).
-define(STATS_KEYS, -define(STATS_KEYS, [
[ subscriptions_cnt subscriptions_cnt,
, subscriptions_max subscriptions_max,
, inflight_cnt inflight_cnt,
, inflight_max inflight_max,
, mqueue_len mqueue_len,
, mqueue_max mqueue_max,
, mqueue_dropped mqueue_dropped,
, next_pkt_id next_pkt_id,
, awaiting_rel_cnt awaiting_rel_cnt,
, awaiting_rel_max awaiting_rel_max
]). ]).
-define(DEFAULT_BATCH_N, 1000). -define(DEFAULT_BATCH_N, 1000).
-type options() :: #{ max_subscriptions => non_neg_integer() -type options() :: #{
, upgrade_qos => boolean() max_subscriptions => non_neg_integer(),
, retry_interval => timeout() upgrade_qos => boolean(),
, max_awaiting_rel => non_neg_integer() | infinity retry_interval => timeout(),
, await_rel_timeout => timeout() max_awaiting_rel => non_neg_integer() | infinity,
, max_inflight => integer() await_rel_timeout => timeout(),
, mqueue => emqx_mqueue:options() max_inflight => integer(),
, is_persistent => boolean() mqueue => emqx_mqueue:options(),
, clientid => emqx_types:clientid() is_persistent => boolean(),
}. clientid => emqx_types:clientid()
}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Init a Session %% Init a Session
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(init(options()) -> session()). -spec init(options()) -> session().
init(Opts) -> init(Opts) ->
MaxInflight = maps:get(max_inflight, Opts, 1), MaxInflight = maps:get(max_inflight, Opts, 1),
QueueOpts = maps:merge( QueueOpts = maps:merge(
#{max_len => 1000, #{
store_qos0 => true max_len => 1000,
}, maps:get(mqueue, Opts, #{})), store_qos0 => true
},
maps:get(mqueue, Opts, #{})
),
#session{ #session{
id = emqx_guid:gen(), id = emqx_guid:gen(),
clientid = maps:get(clientid, Opts, <<>>), clientid = maps:get(clientid, Opts, <<>>),
is_persistent = maps:get(is_persistent, Opts, false), is_persistent = maps:get(is_persistent, Opts, false),
max_subscriptions = maps:get(max_subscriptions, Opts, infinity), max_subscriptions = maps:get(max_subscriptions, Opts, infinity),
subscriptions = #{}, subscriptions = #{},
upgrade_qos = maps:get(upgrade_qos, Opts, false), upgrade_qos = maps:get(upgrade_qos, Opts, false),
inflight = emqx_inflight:new(MaxInflight), inflight = emqx_inflight:new(MaxInflight),
mqueue = emqx_mqueue:init(QueueOpts), mqueue = emqx_mqueue:init(QueueOpts),
next_pkt_id = 1, next_pkt_id = 1,
retry_interval = maps:get(retry_interval, Opts, 30000), retry_interval = maps:get(retry_interval, Opts, 30000),
awaiting_rel = #{}, awaiting_rel = #{},
max_awaiting_rel = maps:get(max_awaiting_rel, Opts, 100), max_awaiting_rel = maps:get(max_awaiting_rel, Opts, 100),
await_rel_timeout = maps:get(await_rel_timeout, Opts, 300000), await_rel_timeout = maps:get(await_rel_timeout, Opts, 300000),
created_at = erlang:system_time(millisecond) created_at = erlang:system_time(millisecond)
}. }.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Info, Stats %% Info, Stats
@ -221,7 +231,7 @@ is_session(#session{}) -> true;
is_session(_) -> false. is_session(_) -> false.
%% @doc Get infos of the session. %% @doc Get infos of the session.
-spec(info(session()) -> emqx_types:infos()). -spec info(session()) -> emqx_types:infos().
info(Session) -> info(Session) ->
maps:from_list(info(?INFO_KEYS, Session)). maps:from_list(info(?INFO_KEYS, Session)).
@ -269,7 +279,7 @@ info(created_at, #session{created_at = CreatedAt}) ->
CreatedAt. CreatedAt.
%% @doc Get stats of the session. %% @doc Get stats of the session.
-spec(stats(session()) -> emqx_types:stats()). -spec stats(session()) -> emqx_types:stats().
stats(Session) -> info(?STATS_KEYS, Session). stats(Session) -> info(?STATS_KEYS, Session).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -278,58 +288,80 @@ stats(Session) -> info(?STATS_KEYS, Session).
ignore_local(ClientInfo, Delivers, Subscriber, Session) -> ignore_local(ClientInfo, Delivers, Subscriber, Session) ->
Subs = info(subscriptions, Session), Subs = info(subscriptions, Session),
lists:dropwhile(fun({deliver, Topic, #message{from = Publisher} = Msg}) -> lists:dropwhile(
case maps:find(Topic, Subs) of fun({deliver, Topic, #message{from = Publisher} = Msg}) ->
{ok, #{nl := 1}} when Subscriber =:= Publisher -> case maps:find(Topic, Subs) of
ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, no_local]), {ok, #{nl := 1}} when Subscriber =:= Publisher ->
ok = emqx_metrics:inc('delivery.dropped'), ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, no_local]),
ok = emqx_metrics:inc('delivery.dropped.no_local'), ok = emqx_metrics:inc('delivery.dropped'),
true; ok = emqx_metrics:inc('delivery.dropped.no_local'),
_ -> true;
false _ ->
end false
end, Delivers). end
end,
Delivers
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Client -> Broker: SUBSCRIBE %% Client -> Broker: SUBSCRIBE
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(subscribe(emqx_types:clientinfo(), emqx_types:topic(), -spec subscribe(
emqx_types:subopts(), session()) emqx_types:clientinfo(),
-> {ok, session()} | {error, emqx_types:reason_code()}). emqx_types:topic(),
subscribe(ClientInfo = #{clientid := ClientId}, TopicFilter, SubOpts, emqx_types:subopts(),
Session = #session{id = SessionID, is_persistent = IsPS, subscriptions = Subs}) -> session()
) ->
{ok, session()} | {error, emqx_types:reason_code()}.
subscribe(
ClientInfo = #{clientid := ClientId},
TopicFilter,
SubOpts,
Session = #session{id = SessionID, is_persistent = IsPS, subscriptions = Subs}
) ->
IsNew = not maps:is_key(TopicFilter, Subs), IsNew = not maps:is_key(TopicFilter, Subs),
case IsNew andalso is_subscriptions_full(Session) of case IsNew andalso is_subscriptions_full(Session) of
false -> false ->
ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts), ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts),
ok = emqx_persistent_session:add_subscription(TopicFilter, SessionID, IsPS), ok = emqx_persistent_session:add_subscription(TopicFilter, SessionID, IsPS),
ok = emqx_hooks:run('session.subscribed', ok = emqx_hooks:run(
[ClientInfo, TopicFilter, SubOpts#{is_new => IsNew}]), 'session.subscribed',
[ClientInfo, TopicFilter, SubOpts#{is_new => IsNew}]
),
{ok, Session#session{subscriptions = maps:put(TopicFilter, SubOpts, Subs)}}; {ok, Session#session{subscriptions = maps:put(TopicFilter, SubOpts, Subs)}};
true -> {error, ?RC_QUOTA_EXCEEDED} true ->
{error, ?RC_QUOTA_EXCEEDED}
end. end.
is_subscriptions_full(#session{max_subscriptions = infinity}) -> is_subscriptions_full(#session{max_subscriptions = infinity}) ->
false; false;
is_subscriptions_full(#session{subscriptions = Subs, is_subscriptions_full(#session{
max_subscriptions = MaxLimit}) -> subscriptions = Subs,
max_subscriptions = MaxLimit
}) ->
maps:size(Subs) >= MaxLimit. maps:size(Subs) >= MaxLimit.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Client -> Broker: UNSUBSCRIBE %% Client -> Broker: UNSUBSCRIBE
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(unsubscribe(emqx_types:clientinfo(), emqx_types:topic(), emqx_types:subopts(), session()) -spec unsubscribe(emqx_types:clientinfo(), emqx_types:topic(), emqx_types:subopts(), session()) ->
-> {ok, session()} | {error, emqx_types:reason_code()}). {ok, session()} | {error, emqx_types:reason_code()}.
unsubscribe(ClientInfo, TopicFilter, UnSubOpts, unsubscribe(
Session = #session{id = SessionID, subscriptions = Subs, is_persistent = IsPS}) -> ClientInfo,
TopicFilter,
UnSubOpts,
Session = #session{id = SessionID, subscriptions = Subs, is_persistent = IsPS}
) ->
case maps:find(TopicFilter, Subs) of case maps:find(TopicFilter, Subs) of
{ok, SubOpts} -> {ok, SubOpts} ->
ok = emqx_broker:unsubscribe(TopicFilter), ok = emqx_broker:unsubscribe(TopicFilter),
ok = emqx_persistent_session:remove_subscription(TopicFilter, SessionID, IsPS), ok = emqx_persistent_session:remove_subscription(TopicFilter, SessionID, IsPS),
ok = emqx_hooks:run('session.unsubscribed', ok = emqx_hooks:run(
[ClientInfo, TopicFilter, maps:merge(SubOpts, UnSubOpts)]), 'session.unsubscribed',
[ClientInfo, TopicFilter, maps:merge(SubOpts, UnSubOpts)]
),
{ok, Session#session{subscriptions = maps:remove(TopicFilter, Subs)}}; {ok, Session#session{subscriptions = maps:remove(TopicFilter, Subs)}};
error -> error ->
{error, ?RC_NO_SUBSCRIPTION_EXISTED} {error, ?RC_NO_SUBSCRIPTION_EXISTED}
@ -339,11 +371,15 @@ unsubscribe(ClientInfo, TopicFilter, UnSubOpts,
%% Client -> Broker: PUBLISH %% Client -> Broker: PUBLISH
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(publish(emqx_types:clientinfo(), emqx_types:packet_id(), emqx_types:message(), session()) -spec publish(emqx_types:clientinfo(), emqx_types:packet_id(), emqx_types:message(), session()) ->
-> {ok, emqx_types:publish_result(), session()} {ok, emqx_types:publish_result(), session()}
| {error, emqx_types:reason_code()}). | {error, emqx_types:reason_code()}.
publish(_ClientInfo, PacketId, Msg = #message{qos = ?QOS_2, timestamp = Ts}, publish(
Session = #session{awaiting_rel = AwaitingRel}) -> _ClientInfo,
PacketId,
Msg = #message{qos = ?QOS_2, timestamp = Ts},
Session = #session{awaiting_rel = AwaitingRel}
) ->
case is_awaiting_full(Session) of case is_awaiting_full(Session) of
false -> false ->
case maps:is_key(PacketId, AwaitingRel) of case maps:is_key(PacketId, AwaitingRel) of
@ -354,27 +390,29 @@ publish(_ClientInfo, PacketId, Msg = #message{qos = ?QOS_2, timestamp = Ts},
true -> true ->
{error, ?RC_PACKET_IDENTIFIER_IN_USE} {error, ?RC_PACKET_IDENTIFIER_IN_USE}
end; end;
true -> {error, ?RC_RECEIVE_MAXIMUM_EXCEEDED} true ->
{error, ?RC_RECEIVE_MAXIMUM_EXCEEDED}
end; end;
%% Publish QoS0/1 directly %% Publish QoS0/1 directly
publish(_ClientInfo, _PacketId, Msg, Session) -> publish(_ClientInfo, _PacketId, Msg, Session) ->
{ok, emqx_broker:publish(Msg), Session}. {ok, emqx_broker:publish(Msg), Session}.
is_awaiting_full(#session{max_awaiting_rel = infinity}) -> is_awaiting_full(#session{max_awaiting_rel = infinity}) ->
false; false;
is_awaiting_full(#session{awaiting_rel = AwaitingRel, is_awaiting_full(#session{
max_awaiting_rel = MaxLimit}) -> awaiting_rel = AwaitingRel,
max_awaiting_rel = MaxLimit
}) ->
maps:size(AwaitingRel) >= MaxLimit. maps:size(AwaitingRel) >= MaxLimit.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Client -> Broker: PUBACK %% Client -> Broker: PUBACK
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(puback(emqx_types:clientinfo(), emqx_types:packet_id(), session()) -spec puback(emqx_types:clientinfo(), emqx_types:packet_id(), session()) ->
-> {ok, emqx_types:message(), session()} {ok, emqx_types:message(), session()}
| {ok, emqx_types:message(), replies(), session()} | {ok, emqx_types:message(), replies(), session()}
| {error, emqx_types:reason_code()}). | {error, emqx_types:reason_code()}.
puback(ClientInfo, PacketId, Session = #session{inflight = Inflight}) -> puback(ClientInfo, PacketId, Session = #session{inflight = Inflight}) ->
case emqx_inflight:lookup(PacketId, Inflight) of case emqx_inflight:lookup(PacketId, Inflight) of
{value, #inflight_data{phase = wait_ack, message = Msg}} -> {value, #inflight_data{phase = wait_ack, message = Msg}} ->
@ -396,9 +434,9 @@ return_with(Msg, {ok, Publishes, Session}) ->
%% Client -> Broker: PUBREC %% Client -> Broker: PUBREC
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(pubrec(emqx_types:clientinfo(), emqx_types:packet_id(), session()) -spec pubrec(emqx_types:clientinfo(), emqx_types:packet_id(), session()) ->
-> {ok, emqx_types:message(), session()} {ok, emqx_types:message(), session()}
| {error, emqx_types:reason_code()}). | {error, emqx_types:reason_code()}.
pubrec(_ClientInfo, PacketId, Session = #session{inflight = Inflight}) -> pubrec(_ClientInfo, PacketId, Session = #session{inflight = Inflight}) ->
case emqx_inflight:lookup(PacketId, Inflight) of case emqx_inflight:lookup(PacketId, Inflight) of
{value, #inflight_data{phase = wait_ack, message = Msg} = Data} -> {value, #inflight_data{phase = wait_ack, message = Msg} = Data} ->
@ -415,8 +453,8 @@ pubrec(_ClientInfo, PacketId, Session = #session{inflight = Inflight}) ->
%% Client -> Broker: PUBREL %% Client -> Broker: PUBREL
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(pubrel(emqx_types:clientinfo(), emqx_types:packet_id(), session()) -spec pubrel(emqx_types:clientinfo(), emqx_types:packet_id(), session()) ->
-> {ok, session()} | {error, emqx_types:reason_code()}). {ok, session()} | {error, emqx_types:reason_code()}.
pubrel(_ClientInfo, PacketId, Session = #session{awaiting_rel = AwaitingRel}) -> pubrel(_ClientInfo, PacketId, Session = #session{awaiting_rel = AwaitingRel}) ->
case maps:take(PacketId, AwaitingRel) of case maps:take(PacketId, AwaitingRel) of
{_Ts, AwaitingRel1} -> {_Ts, AwaitingRel1} ->
@ -429,9 +467,10 @@ pubrel(_ClientInfo, PacketId, Session = #session{awaiting_rel = AwaitingRel}) ->
%% Client -> Broker: PUBCOMP %% Client -> Broker: PUBCOMP
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(pubcomp(emqx_types:clientinfo(), emqx_types:packet_id(), session()) -spec pubcomp(emqx_types:clientinfo(), emqx_types:packet_id(), session()) ->
-> {ok, session()} | {ok, replies(), session()} {ok, session()}
| {error, emqx_types:reason_code()}). | {ok, replies(), session()}
| {error, emqx_types:reason_code()}.
pubcomp(ClientInfo, PacketId, Session = #session{inflight = Inflight}) -> pubcomp(ClientInfo, PacketId, Session = #session{inflight = Inflight}) ->
case emqx_inflight:lookup(PacketId, Inflight) of case emqx_inflight:lookup(PacketId, Inflight) of
{value, #inflight_data{phase = wait_comp, message = Msg}} -> {value, #inflight_data{phase = wait_comp, message = Msg}} ->
@ -450,7 +489,8 @@ pubcomp(ClientInfo, PacketId, Session = #session{inflight = Inflight}) ->
dequeue(ClientInfo, Session = #session{inflight = Inflight, mqueue = Q}) -> dequeue(ClientInfo, Session = #session{inflight = Inflight, mqueue = Q}) ->
case emqx_mqueue:is_empty(Q) of case emqx_mqueue:is_empty(Q) of
true -> {ok, Session}; true ->
{ok, Session};
false -> false ->
{Msgs, Q1} = dequeue(ClientInfo, batch_n(Inflight), [], Q), {Msgs, Q1} = dequeue(ClientInfo, batch_n(Inflight), [], Q),
do_deliver(ClientInfo, Msgs, [], Session#session{mqueue = Q1}) do_deliver(ClientInfo, Msgs, [], Session#session{mqueue = Q1})
@ -458,17 +498,18 @@ dequeue(ClientInfo, Session = #session{inflight = Inflight, mqueue = Q}) ->
dequeue(_ClientInfo, 0, Msgs, Q) -> dequeue(_ClientInfo, 0, Msgs, Q) ->
{lists:reverse(Msgs), Q}; {lists:reverse(Msgs), Q};
dequeue(ClientInfo, Cnt, Msgs, Q) -> dequeue(ClientInfo, Cnt, Msgs, Q) ->
case emqx_mqueue:out(Q) of case emqx_mqueue:out(Q) of
{empty, _Q} -> dequeue(ClientInfo, 0, Msgs, Q); {empty, _Q} ->
dequeue(ClientInfo, 0, Msgs, Q);
{{value, Msg}, Q1} -> {{value, Msg}, Q1} ->
case emqx_message:is_expired(Msg) of case emqx_message:is_expired(Msg) of
true -> true ->
ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, expired]), ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, expired]),
ok = inc_delivery_expired_cnt(), ok = inc_delivery_expired_cnt(),
dequeue(ClientInfo, Cnt, Msgs, Q1); dequeue(ClientInfo, Cnt, Msgs, Q1);
false -> dequeue(ClientInfo, acc_cnt(Msg, Cnt), [Msg|Msgs], Q1) false ->
dequeue(ClientInfo, acc_cnt(Msg, Cnt), [Msg | Msgs], Q1)
end end
end. end.
@ -479,40 +520,45 @@ acc_cnt(_Msg, Cnt) -> Cnt - 1.
%% Broker -> Client: Deliver %% Broker -> Client: Deliver
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(deliver(emqx_types:clientinfo(), list(emqx_types:deliver()), session()) -spec deliver(emqx_types:clientinfo(), list(emqx_types:deliver()), session()) ->
-> {ok, session()} | {ok, replies(), session()}). {ok, session()} | {ok, replies(), session()}.
deliver(ClientInfo, [Deliver], Session) -> %% Optimize %% Optimize
deliver(ClientInfo, [Deliver], Session) ->
Msg = enrich_deliver(Deliver, Session), Msg = enrich_deliver(Deliver, Session),
deliver_msg(ClientInfo, Msg, Session); deliver_msg(ClientInfo, Msg, Session);
deliver(ClientInfo, Delivers, Session) -> deliver(ClientInfo, Delivers, Session) ->
Msgs = [enrich_deliver(D, Session) || D <- Delivers], Msgs = [enrich_deliver(D, Session) || D <- Delivers],
do_deliver(ClientInfo, Msgs, [], Session). do_deliver(ClientInfo, Msgs, [], Session).
do_deliver(_ClientInfo, [], Publishes, Session) -> do_deliver(_ClientInfo, [], Publishes, Session) ->
{ok, lists:reverse(Publishes), Session}; {ok, lists:reverse(Publishes), Session};
do_deliver(ClientInfo, [Msg | More], Acc, Session) -> do_deliver(ClientInfo, [Msg | More], Acc, Session) ->
case deliver_msg(ClientInfo, Msg, Session) of case deliver_msg(ClientInfo, Msg, Session) of
{ok, Session1} -> {ok, Session1} ->
do_deliver(ClientInfo, More, Acc, Session1); do_deliver(ClientInfo, More, Acc, Session1);
{ok, [Publish], Session1} -> {ok, [Publish], Session1} ->
do_deliver(ClientInfo, More, [Publish|Acc], Session1) do_deliver(ClientInfo, More, [Publish | Acc], Session1)
end. end.
deliver_msg(_ClientInfo, Msg = #message{qos = ?QOS_0}, Session) -> deliver_msg(_ClientInfo, Msg = #message{qos = ?QOS_0}, Session) ->
on_delivery_completed(Msg, Session), % %
on_delivery_completed(Msg, Session),
{ok, [{undefined, maybe_ack(Msg)}], Session}; {ok, [{undefined, maybe_ack(Msg)}], Session};
deliver_msg(
deliver_msg(ClientInfo, Msg = #message{qos = QoS}, Session = ClientInfo,
#session{next_pkt_id = PacketId, inflight = Inflight}) Msg = #message{qos = QoS},
when QoS =:= ?QOS_1 orelse QoS =:= ?QOS_2 -> Session =
#session{next_pkt_id = PacketId, inflight = Inflight}
) when
QoS =:= ?QOS_1 orelse QoS =:= ?QOS_2
->
case emqx_inflight:is_full(Inflight) of case emqx_inflight:is_full(Inflight) of
true -> true ->
Session1 = case maybe_nack(Msg) of Session1 =
true -> Session; case maybe_nack(Msg) of
false -> enqueue(ClientInfo, Msg, Session) true -> Session;
end, false -> enqueue(ClientInfo, Msg, Session)
end,
{ok, Session1}; {ok, Session1};
false -> false ->
Publish = {PacketId, maybe_ack(Msg)}, Publish = {PacketId, maybe_ack(Msg)},
@ -521,14 +567,20 @@ deliver_msg(ClientInfo, Msg = #message{qos = QoS}, Session =
{ok, [Publish], next_pkt_id(Session1)} {ok, [Publish], next_pkt_id(Session1)}
end. end.
-spec(enqueue(emqx_types:clientinfo(), list(emqx_types:deliver())|emqx_types:message(), -spec enqueue(
session()) -> session()). emqx_types:clientinfo(),
list(emqx_types:deliver()) | emqx_types:message(),
session()
) -> session().
enqueue(ClientInfo, Delivers, Session) when is_list(Delivers) -> enqueue(ClientInfo, Delivers, Session) when is_list(Delivers) ->
lists:foldl(fun(Deliver, Session0) -> lists:foldl(
fun(Deliver, Session0) ->
Msg = enrich_deliver(Deliver, Session), Msg = enrich_deliver(Deliver, Session),
enqueue(ClientInfo, Msg, Session0) enqueue(ClientInfo, Msg, Session0)
end, Session, Delivers); end,
Session,
Delivers
);
enqueue(ClientInfo, #message{} = Msg, Session = #session{mqueue = Q}) -> enqueue(ClientInfo, #message{} = Msg, Session = #session{mqueue = Q}) ->
{Dropped, NewQ} = emqx_mqueue:in(Msg, Q), {Dropped, NewQ} = emqx_mqueue:in(Msg, Q),
(Dropped =/= undefined) andalso handle_dropped(ClientInfo, Dropped, Session), (Dropped =/= undefined) andalso handle_dropped(ClientInfo, Dropped, Session),
@ -536,25 +588,37 @@ enqueue(ClientInfo, #message{} = Msg, Session = #session{mqueue = Q}) ->
handle_dropped(ClientInfo, Msg = #message{qos = QoS, topic = Topic}, #session{mqueue = Q}) -> handle_dropped(ClientInfo, Msg = #message{qos = QoS, topic = Topic}, #session{mqueue = Q}) ->
Payload = emqx_message:to_log_map(Msg), Payload = emqx_message:to_log_map(Msg),
#{store_qos0 := StoreQos0} = QueueInfo = emqx_mqueue:info(Q), #{store_qos0 := StoreQos0} = QueueInfo = emqx_mqueue:info(Q),
case (QoS == ?QOS_0) andalso (not StoreQos0) of case (QoS == ?QOS_0) andalso (not StoreQos0) of
true -> true ->
ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, qos0_msg]), ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, qos0_msg]),
ok = emqx_metrics:inc('delivery.dropped'), ok = emqx_metrics:inc('delivery.dropped'),
ok = emqx_metrics:inc('delivery.dropped.qos0_msg'), ok = emqx_metrics:inc('delivery.dropped.qos0_msg'),
ok = inc_pd('send_msg.dropped'), ok = inc_pd('send_msg.dropped'),
?SLOG(warning, #{msg => "dropped_qos0_msg", ?SLOG(
queue => QueueInfo, warning,
payload => Payload}, #{topic => Topic}); #{
msg => "dropped_qos0_msg",
queue => QueueInfo,
payload => Payload
},
#{topic => Topic}
);
false -> false ->
ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, queue_full]), ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, queue_full]),
ok = emqx_metrics:inc('delivery.dropped'), ok = emqx_metrics:inc('delivery.dropped'),
ok = emqx_metrics:inc('delivery.dropped.queue_full'), ok = emqx_metrics:inc('delivery.dropped.queue_full'),
ok = inc_pd('send_msg.dropped'), ok = inc_pd('send_msg.dropped'),
ok = inc_pd('send_msg.dropped.queue_full'), ok = inc_pd('send_msg.dropped.queue_full'),
?SLOG(warning, #{msg => "dropped_msg_due_to_mqueue_is_full", ?SLOG(
queue => QueueInfo, warning,
payload => Payload}, #{topic => Topic}) #{
msg => "dropped_msg_due_to_mqueue_is_full",
queue => QueueInfo,
payload => Payload
},
#{topic => Topic}
)
end. end.
enrich_deliver({deliver, Topic, Msg}, Session = #session{subscriptions = Subs}) -> enrich_deliver({deliver, Topic, Msg}, Session = #session{subscriptions = Subs}) ->
@ -562,13 +626,13 @@ enrich_deliver({deliver, Topic, Msg}, Session = #session{subscriptions = Subs})
maybe_ack(Msg) -> maybe_ack(Msg) ->
case emqx_shared_sub:is_ack_required(Msg) of case emqx_shared_sub:is_ack_required(Msg) of
true -> emqx_shared_sub:maybe_ack(Msg); true -> emqx_shared_sub:maybe_ack(Msg);
false -> Msg false -> Msg
end. end.
maybe_nack(Msg) -> maybe_nack(Msg) ->
emqx_shared_sub:is_ack_required(Msg) emqx_shared_sub:is_ack_required(Msg) andalso
andalso (ok == emqx_shared_sub:maybe_nack_dropped(Msg)). (ok == emqx_shared_sub:maybe_nack_dropped(Msg)).
get_subopts(Topic, SubMap) -> get_subopts(Topic, SubMap) ->
case maps:find(Topic, SubMap) of case maps:find(Topic, SubMap) of
@ -576,27 +640,35 @@ get_subopts(Topic, SubMap) ->
[{nl, Nl}, {qos, QoS}, {rap, Rap}, {subid, SubId}]; [{nl, Nl}, {qos, QoS}, {rap, Rap}, {subid, SubId}];
{ok, #{nl := Nl, qos := QoS, rap := Rap}} -> {ok, #{nl := Nl, qos := QoS, rap := Rap}} ->
[{nl, Nl}, {qos, QoS}, {rap, Rap}]; [{nl, Nl}, {qos, QoS}, {rap, Rap}];
error -> [] error ->
[]
end. end.
enrich_subopts([], Msg, _Session) -> Msg; enrich_subopts([], Msg, _Session) ->
enrich_subopts([{nl, 1}|Opts], Msg, Session) -> Msg;
enrich_subopts([{nl, 1} | Opts], Msg, Session) ->
enrich_subopts(Opts, emqx_message:set_flag(nl, Msg), Session); enrich_subopts(Opts, emqx_message:set_flag(nl, Msg), Session);
enrich_subopts([{nl, 0}|Opts], Msg, Session) -> enrich_subopts([{nl, 0} | Opts], Msg, Session) ->
enrich_subopts(Opts, Msg, Session); enrich_subopts(Opts, Msg, Session);
enrich_subopts([{qos, SubQoS}|Opts], Msg = #message{qos = PubQoS}, enrich_subopts(
Session = #session{upgrade_qos = true}) -> [{qos, SubQoS} | Opts],
Msg = #message{qos = PubQoS},
Session = #session{upgrade_qos = true}
) ->
enrich_subopts(Opts, Msg#message{qos = max(SubQoS, PubQoS)}, Session); enrich_subopts(Opts, Msg#message{qos = max(SubQoS, PubQoS)}, Session);
enrich_subopts([{qos, SubQoS}|Opts], Msg = #message{qos = PubQoS}, enrich_subopts(
Session = #session{upgrade_qos = false}) -> [{qos, SubQoS} | Opts],
Msg = #message{qos = PubQoS},
Session = #session{upgrade_qos = false}
) ->
enrich_subopts(Opts, Msg#message{qos = min(SubQoS, PubQoS)}, Session); enrich_subopts(Opts, Msg#message{qos = min(SubQoS, PubQoS)}, Session);
enrich_subopts([{rap, 1}|Opts], Msg, Session) -> enrich_subopts([{rap, 1} | Opts], Msg, Session) ->
enrich_subopts(Opts, Msg, Session); enrich_subopts(Opts, Msg, Session);
enrich_subopts([{rap, 0}|Opts], Msg = #message{headers = #{retained := true}}, Session) -> enrich_subopts([{rap, 0} | Opts], Msg = #message{headers = #{retained := true}}, Session) ->
enrich_subopts(Opts, Msg, Session); enrich_subopts(Opts, Msg, Session);
enrich_subopts([{rap, 0}|Opts], Msg, Session) -> enrich_subopts([{rap, 0} | Opts], Msg, Session) ->
enrich_subopts(Opts, emqx_message:set_flag(retain, false, Msg), Session); enrich_subopts(Opts, emqx_message:set_flag(retain, false, Msg), Session);
enrich_subopts([{subid, SubId}|Opts], Msg, Session) -> enrich_subopts([{subid, SubId} | Opts], Msg, Session) ->
Props = emqx_message:get_header(properties, Msg, #{}), Props = emqx_message:get_header(properties, Msg, #{}),
Msg1 = emqx_message:set_header(properties, Props#{'Subscription-Identifier' => SubId}, Msg), Msg1 = emqx_message:set_header(properties, Props#{'Subscription-Identifier' => SubId}, Msg),
enrich_subopts(Opts, Msg1, Session). enrich_subopts(Opts, Msg1, Session).
@ -613,22 +685,32 @@ await(PacketId, Msg, Session = #session{inflight = Inflight}) ->
%% Retry Delivery %% Retry Delivery
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(retry(emqx_types:clientinfo(), session()) -> -spec retry(emqx_types:clientinfo(), session()) ->
{ok, session()} | {ok, replies(), timeout(), session()}). {ok, session()} | {ok, replies(), timeout(), session()}.
retry(ClientInfo, Session = #session{inflight = Inflight}) -> retry(ClientInfo, Session = #session{inflight = Inflight}) ->
case emqx_inflight:is_empty(Inflight) of case emqx_inflight:is_empty(Inflight) of
true -> {ok, Session}; true ->
{ok, Session};
false -> false ->
Now = erlang:system_time(millisecond), Now = erlang:system_time(millisecond),
retry_delivery(emqx_inflight:to_list(fun sort_fun/2, Inflight), retry_delivery(
[], Now, Session, ClientInfo) emqx_inflight:to_list(fun sort_fun/2, Inflight),
[],
Now,
Session,
ClientInfo
)
end. end.
retry_delivery([], Acc, _Now, Session = #session{retry_interval = Interval}, _ClientInfo) -> retry_delivery([], Acc, _Now, Session = #session{retry_interval = Interval}, _ClientInfo) ->
{ok, lists:reverse(Acc), Interval, Session}; {ok, lists:reverse(Acc), Interval, Session};
retry_delivery(
retry_delivery([{PacketId, #inflight_data{timestamp = Ts} = Data} | More], [{PacketId, #inflight_data{timestamp = Ts} = Data} | More],
Acc, Now, Session = #session{retry_interval = Interval, inflight = Inflight}, ClientInfo) -> Acc,
Now,
Session = #session{retry_interval = Interval, inflight = Inflight},
ClientInfo
) ->
case (Age = age(Now, Ts)) >= Interval of case (Age = age(Now, Ts)) >= Interval of
true -> true ->
{Acc1, Inflight1} = do_retry_delivery(PacketId, Data, Now, Acc, Inflight, ClientInfo), {Acc1, Inflight1} = do_retry_delivery(PacketId, Data, Now, Acc, Inflight, ClientInfo),
@ -637,8 +719,14 @@ retry_delivery([{PacketId, #inflight_data{timestamp = Ts} = Data} | More],
{ok, lists:reverse(Acc), Interval - max(0, Age), Session} {ok, lists:reverse(Acc), Interval - max(0, Age), Session}
end. end.
do_retry_delivery(PacketId, #inflight_data{phase = wait_ack, message = Msg} = Data, do_retry_delivery(
Now, Acc, Inflight, ClientInfo) -> PacketId,
#inflight_data{phase = wait_ack, message = Msg} = Data,
Now,
Acc,
Inflight,
ClientInfo
) ->
case emqx_message:is_expired(Msg) of case emqx_message:is_expired(Msg) of
true -> true ->
ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, expired]), ok = emqx_hooks:run('delivery.dropped', [ClientInfo, Msg, expired]),
@ -650,7 +738,6 @@ do_retry_delivery(PacketId, #inflight_data{phase = wait_ack, message = Msg} = Da
Inflight1 = emqx_inflight:update(PacketId, Update, Inflight), Inflight1 = emqx_inflight:update(PacketId, Update, Inflight),
{[{PacketId, Msg1} | Acc], Inflight1} {[{PacketId, Msg1} | Acc], Inflight1}
end; end;
do_retry_delivery(PacketId, Data, Now, Acc, Inflight, _) -> do_retry_delivery(PacketId, Data, Now, Acc, Inflight, _) ->
Update = Data#inflight_data{timestamp = Now}, Update = Data#inflight_data{timestamp = Now},
Inflight1 = emqx_inflight:update(PacketId, Update, Inflight), Inflight1 = emqx_inflight:update(PacketId, Update, Inflight),
@ -660,16 +747,21 @@ do_retry_delivery(PacketId, Data, Now, Acc, Inflight, _) ->
%% Expire Awaiting Rel %% Expire Awaiting Rel
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(expire(emqx_types:clientinfo(), awaiting_rel, session()) -> -spec expire(emqx_types:clientinfo(), awaiting_rel, session()) ->
{ok, session()} | {ok, timeout(), session()}). {ok, session()} | {ok, timeout(), session()}.
expire(_ClientInfo, awaiting_rel, Session = #session{awaiting_rel = AwaitingRel}) -> expire(_ClientInfo, awaiting_rel, Session = #session{awaiting_rel = AwaitingRel}) ->
case maps:size(AwaitingRel) of case maps:size(AwaitingRel) of
0 -> {ok, Session}; 0 -> {ok, Session};
_ -> expire_awaiting_rel(erlang:system_time(millisecond), Session) _ -> expire_awaiting_rel(erlang:system_time(millisecond), Session)
end. end.
expire_awaiting_rel(Now, Session = #session{awaiting_rel = AwaitingRel, expire_awaiting_rel(
await_rel_timeout = Timeout}) -> Now,
Session = #session{
awaiting_rel = AwaitingRel,
await_rel_timeout = Timeout
}
) ->
NotExpired = fun(_PacketId, Ts) -> age(Now, Ts) < Timeout end, NotExpired = fun(_PacketId, Ts) -> age(Now, Ts) < Timeout end,
AwaitingRel1 = maps:filter(NotExpired, AwaitingRel), AwaitingRel1 = maps:filter(NotExpired, AwaitingRel),
ExpiredCnt = maps:size(AwaitingRel) - maps:size(AwaitingRel1), ExpiredCnt = maps:size(AwaitingRel) - maps:size(AwaitingRel1),
@ -684,32 +776,38 @@ expire_awaiting_rel(Now, Session = #session{awaiting_rel = AwaitingRel,
%% Takeover, Resume and Replay %% Takeover, Resume and Replay
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(takeover(session()) -> ok). -spec takeover(session()) -> ok.
takeover(#session{subscriptions = Subs}) -> takeover(#session{subscriptions = Subs}) ->
lists:foreach(fun emqx_broker:unsubscribe/1, maps:keys(Subs)). lists:foreach(fun emqx_broker:unsubscribe/1, maps:keys(Subs)).
-spec(resume(emqx_types:clientinfo(), session()) -> ok). -spec resume(emqx_types:clientinfo(), session()) -> ok.
resume(ClientInfo = #{clientid := ClientId}, Session = #session{subscriptions = Subs}) -> resume(ClientInfo = #{clientid := ClientId}, Session = #session{subscriptions = Subs}) ->
lists:foreach(fun({TopicFilter, SubOpts}) -> lists:foreach(
ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts) fun({TopicFilter, SubOpts}) ->
end, maps:to_list(Subs)), ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts)
end,
maps:to_list(Subs)
),
ok = emqx_metrics:inc('session.resumed'), ok = emqx_metrics:inc('session.resumed'),
emqx_hooks:run('session.resumed', [ClientInfo, info(Session)]). emqx_hooks:run('session.resumed', [ClientInfo, info(Session)]).
-spec(replay(emqx_types:clientinfo(), session()) -> {ok, replies(), session()}). -spec replay(emqx_types:clientinfo(), session()) -> {ok, replies(), session()}.
replay(ClientInfo, Session = #session{inflight = Inflight}) -> replay(ClientInfo, Session = #session{inflight = Inflight}) ->
Pubs = lists:map(fun({PacketId, #inflight_data{phase = wait_comp}}) -> Pubs = lists:map(
{pubrel, PacketId}; fun
({PacketId, #inflight_data{message = Msg}}) -> ({PacketId, #inflight_data{phase = wait_comp}}) ->
{PacketId, emqx_message:set_flag(dup, true, Msg)} {pubrel, PacketId};
end, emqx_inflight:to_list(Inflight)), ({PacketId, #inflight_data{message = Msg}}) ->
{PacketId, emqx_message:set_flag(dup, true, Msg)}
end,
emqx_inflight:to_list(Inflight)
),
case dequeue(ClientInfo, Session) of case dequeue(ClientInfo, Session) of
{ok, NSession} -> {ok, Pubs, NSession}; {ok, NSession} -> {ok, Pubs, NSession};
{ok, More, NSession} -> {ok, More, NSession} -> {ok, lists:append(Pubs, More), NSession}
{ok, lists:append(Pubs, More), NSession}
end. end.
-spec(terminate(emqx_types:clientinfo(), Reason :: term(), session()) -> ok). -spec terminate(emqx_types:clientinfo(), Reason :: term(), session()) -> ok.
terminate(ClientInfo, discarded, Session) -> terminate(ClientInfo, discarded, Session) ->
run_hook('session.discarded', [ClientInfo, info(Session)]); run_hook('session.discarded', [ClientInfo, info(Session)]);
terminate(ClientInfo, takenover, Session) -> terminate(ClientInfo, takenover, Session) ->
@ -718,7 +816,8 @@ terminate(ClientInfo, Reason, Session) ->
run_hook('session.terminated', [ClientInfo, Reason, info(Session)]). run_hook('session.terminated', [ClientInfo, Reason, info(Session)]).
run_hook(Name, Args) -> run_hook(Name, Args) ->
ok = emqx_metrics:inc(Name), emqx_hooks:run(Name, Args). ok = emqx_metrics:inc(Name),
emqx_hooks:run(Name, Args).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Inc message/delivery expired counter %% Inc message/delivery expired counter
@ -753,18 +852,23 @@ obtain_next_pkt_id(Session) ->
next_pkt_id(Session = #session{next_pkt_id = ?MAX_PACKET_ID}) -> next_pkt_id(Session = #session{next_pkt_id = ?MAX_PACKET_ID}) ->
Session#session{next_pkt_id = 1}; Session#session{next_pkt_id = 1};
next_pkt_id(Session = #session{next_pkt_id = Id}) -> next_pkt_id(Session = #session{next_pkt_id = Id}) ->
Session#session{next_pkt_id = Id + 1}. Session#session{next_pkt_id = Id + 1}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Message Latency Stats %% Message Latency Stats
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_delivery_completed(Msg, on_delivery_completed(
#session{created_at = CreateAt, clientid = ClientId}) -> Msg,
emqx:run_hook('delivery.completed', #session{created_at = CreateAt, clientid = ClientId}
[Msg, ) ->
#{session_birth_time => CreateAt, clientid => ClientId}]). emqx:run_hook(
'delivery.completed',
[
Msg,
#{session_birth_time => CreateAt, clientid => ClientId}
]
).
mark_begin_deliver(Msg) -> mark_begin_deliver(Msg) ->
emqx_message:set_header(deliver_begin_at, erlang:system_time(second), Msg). emqx_message:set_header(deliver_begin_at, erlang:system_time(second), Msg).
@ -785,9 +889,11 @@ batch_n(Inflight) ->
end. end.
with_ts(Msg) -> with_ts(Msg) ->
#inflight_data{phase = wait_ack, #inflight_data{
message = Msg, phase = wait_ack,
timestamp = erlang:system_time(millisecond)}. message = Msg,
timestamp = erlang:system_time(millisecond)
}.
age(Now, Ts) -> Now - Ts. age(Now, Ts) -> Now - Ts.
@ -797,4 +903,4 @@ age(Now, Ts) -> Now - Ts.
set_field(Name, Value, Session) -> set_field(Name, Value, Session) ->
Pos = emqx_misc:index_of(Name, record_info(fields, session)), Pos = emqx_misc:index_of(Name, record_info(fields, session)),
setelement(Pos+1, Session, Value). setelement(Pos + 1, Session, Value).

View File

@ -24,37 +24,42 @@
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
-export([ create_init_tab/0 -export([
, create_router_tab/1 create_init_tab/0,
, start_link/2]). create_router_tab/1,
start_link/2
]).
%% Route APIs %% Route APIs
-export([ delete_routes/2 -export([
, do_add_route/2 delete_routes/2,
, do_delete_route/2 do_add_route/2,
, match_routes/1 do_delete_route/2,
]). match_routes/1
]).
-export([ buffer/3 -export([
, pending/2 buffer/3,
, resume_begin/2 pending/2,
, resume_end/2 resume_begin/2,
]). resume_end/2
]).
-export([print_routes/1]). -export([print_routes/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-type(group() :: binary()). -type group() :: binary().
-type(dest() :: node() | {group(), node()}). -type dest() :: node() | {group(), node()}.
-define(ROUTE_RAM_TAB, emqx_session_route_ram). -define(ROUTE_RAM_TAB, emqx_session_route_ram).
-define(ROUTE_DISC_TAB, emqx_session_route_disc). -define(ROUTE_DISC_TAB, emqx_session_route_disc).
@ -67,63 +72,83 @@
create_router_tab(disc) -> create_router_tab(disc) ->
ok = mria:create_table(?ROUTE_DISC_TAB, [ ok = mria:create_table(?ROUTE_DISC_TAB, [
{type, bag}, {type, bag},
{rlog_shard, ?ROUTE_SHARD}, {rlog_shard, ?ROUTE_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, route}, {record_name, route},
{attributes, record_info(fields, route)}, {attributes, record_info(fields, route)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]); {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]);
create_router_tab(ram) -> create_router_tab(ram) ->
ok = mria:create_table(?ROUTE_RAM_TAB, [ ok = mria:create_table(?ROUTE_RAM_TAB, [
{type, bag}, {type, bag},
{rlog_shard, ?ROUTE_SHARD}, {rlog_shard, ?ROUTE_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, route}, {record_name, route},
{attributes, record_info(fields, route)}, {attributes, record_info(fields, route)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]). {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Start a router %% Start a router
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
create_init_tab() -> create_init_tab() ->
emqx_tables:new(?SESSION_INIT_TAB, [public, {read_concurrency, true}, emqx_tables:new(?SESSION_INIT_TAB, [
{write_concurrency, true}]). public,
{read_concurrency, true},
{write_concurrency, true}
]).
-spec(start_link(atom(), pos_integer()) -> startlink_ret()). -spec start_link(atom(), pos_integer()) -> startlink_ret().
start_link(Pool, Id) -> start_link(Pool, Id) ->
gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, gen_server:start_link(
?MODULE, [Pool, Id], [{hibernate_after, 1000}]). {local, emqx_misc:proc_name(?MODULE, Id)},
?MODULE,
[Pool, Id],
[{hibernate_after, 1000}]
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Route APIs %% Route APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(do_add_route(emqx_topic:topic(), dest()) -> ok | {error, term()}). -spec do_add_route(emqx_topic:topic(), dest()) -> ok | {error, term()}.
do_add_route(Topic, SessionID) when is_binary(Topic) -> do_add_route(Topic, SessionID) when is_binary(Topic) ->
Route = #route{topic = Topic, dest = SessionID}, Route = #route{topic = Topic, dest = SessionID},
case lists:member(Route, lookup_routes(Topic)) of case lists:member(Route, lookup_routes(Topic)) of
true -> ok; true ->
ok;
false -> false ->
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> true ->
Fun = fun emqx_router_utils:insert_session_trie_route/2, Fun = fun emqx_router_utils:insert_session_trie_route/2,
emqx_router_utils:maybe_trans(Fun, [route_tab(), Route], emqx_router_utils:maybe_trans(
?PERSISTENT_SESSION_SHARD); Fun,
[route_tab(), Route],
?PERSISTENT_SESSION_SHARD
);
false -> false ->
emqx_router_utils:insert_direct_route(route_tab(), Route) emqx_router_utils:insert_direct_route(route_tab(), Route)
end end
end. end.
%% @doc Match routes %% @doc Match routes
-spec(match_routes(emqx_topic:topic()) -> [emqx_types:route()]). -spec match_routes(emqx_topic:topic()) -> [emqx_types:route()].
match_routes(Topic) when is_binary(Topic) -> match_routes(Topic) when is_binary(Topic) ->
case match_trie(Topic) of case match_trie(Topic) of
[] -> lookup_routes(Topic); [] -> lookup_routes(Topic);
Matched -> Matched -> lists:append([lookup_routes(To) || To <- [Topic | Matched]])
lists:append([lookup_routes(To) || To <- [Topic | Matched]])
end. end.
%% Optimize: routing table will be replicated to all router nodes. %% Optimize: routing table will be replicated to all router nodes.
@ -137,11 +162,11 @@ match_trie(Topic) ->
delete_routes(SessionID, Subscriptions) -> delete_routes(SessionID, Subscriptions) ->
cast(pick(SessionID), {delete_routes, SessionID, Subscriptions}). cast(pick(SessionID), {delete_routes, SessionID, Subscriptions}).
-spec(do_delete_route(emqx_topic:topic(), dest()) -> ok | {error, term()}). -spec do_delete_route(emqx_topic:topic(), dest()) -> ok | {error, term()}.
do_delete_route(Topic, SessionID) -> do_delete_route(Topic, SessionID) ->
Route = #route{topic = Topic, dest = SessionID}, Route = #route{topic = Topic, dest = SessionID},
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> true ->
Fun = fun emqx_router_utils:delete_session_trie_route/2, Fun = fun emqx_router_utils:delete_session_trie_route/2,
emqx_router_utils:maybe_trans(Fun, [route_tab(), Route], ?PERSISTENT_SESSION_SHARD); emqx_router_utils:maybe_trans(Fun, [route_tab(), Route], ?PERSISTENT_SESSION_SHARD);
false -> false ->
@ -149,11 +174,14 @@ do_delete_route(Topic, SessionID) ->
end. end.
%% @doc Print routes to a topic %% @doc Print routes to a topic
-spec(print_routes(emqx_topic:topic()) -> ok). -spec print_routes(emqx_topic:topic()) -> ok.
print_routes(Topic) -> print_routes(Topic) ->
lists:foreach(fun(#route{topic = To, dest = SessionID}) -> lists:foreach(
io:format("~s -> ~p~n", [To, SessionID]) fun(#route{topic = To, dest = SessionID}) ->
end, match_routes(Topic)). io:format("~s -> ~p~n", [To, SessionID])
end,
match_routes(Topic)
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Session APIs %% Session APIs
@ -173,11 +201,11 @@ resume_begin(From, SessionID) when is_pid(From), is_binary(SessionID) ->
call(pick(SessionID), {resume_begin, From, SessionID}). call(pick(SessionID), {resume_begin, From, SessionID}).
-spec resume_end(pid(), binary()) -> -spec resume_end(pid(), binary()) ->
{'ok', [emqx_types:message()]} | {'error', term()}. {'ok', [emqx_types:message()]} | {'error', term()}.
resume_end(From, SessionID) when is_pid(From), is_binary(SessionID) -> resume_end(From, SessionID) when is_pid(From), is_binary(SessionID) ->
case emqx_tables:lookup_value(?SESSION_INIT_TAB, SessionID) of case emqx_tables:lookup_value(?SESSION_INIT_TAB, SessionID) of
undefined -> undefined ->
?tp(ps_session_not_found, #{ sid => SessionID }), ?tp(ps_session_not_found, #{sid => SessionID}),
{error, not_found}; {error, not_found};
Pid -> Pid ->
Res = emqx_session_router_worker:resume_end(From, Pid, SessionID), Res = emqx_session_router_worker:resume_end(From, Pid, SessionID),
@ -237,7 +265,7 @@ handle_cast({resume_end, SessionID, Pid}, State) ->
end, end,
Pmon = emqx_pmon:demonitor(Pid, maps:get(pmon, State)), Pmon = emqx_pmon:demonitor(Pid, maps:get(pmon, State)),
_ = emqx_session_router_worker_sup:abort_worker(Pid), _ = emqx_session_router_worker_sup:abort_worker(Pid),
{noreply, State#{ pmon => Pmon }}; {noreply, State#{pmon => Pmon}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
@ -257,7 +285,7 @@ code_change(_OldVsn, State, _Extra) ->
%% initialisation of a resuming session. %% initialisation of a resuming session.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init_resume_worker(RemotePid, SessionID, #{ pmon := Pmon } = State) -> init_resume_worker(RemotePid, SessionID, #{pmon := Pmon} = State) ->
case emqx_session_router_worker_sup:start_worker(SessionID, RemotePid) of case emqx_session_router_worker_sup:start_worker(SessionID, RemotePid) of
{error, What} -> {error, What} ->
?SLOG(error, #{msg => "failed_to_start_resume_worker", reason => What}), ?SLOG(error, #{msg => "failed_to_start_resume_worker", reason => What}),
@ -266,11 +294,11 @@ init_resume_worker(RemotePid, SessionID, #{ pmon := Pmon } = State) ->
Pmon1 = emqx_pmon:monitor(Pid, Pmon), Pmon1 = emqx_pmon:monitor(Pid, Pmon),
case emqx_tables:lookup_value(?SESSION_INIT_TAB, SessionID) of case emqx_tables:lookup_value(?SESSION_INIT_TAB, SessionID) of
undefined -> undefined ->
{ok, Pid, State#{ pmon => Pmon1 }}; {ok, Pid, State#{pmon => Pmon1}};
{_, OldPid} -> {_, OldPid} ->
Pmon2 = emqx_pmon:demonitor(OldPid, Pmon1), Pmon2 = emqx_pmon:demonitor(OldPid, Pmon1),
_ = emqx_session_router_worker_sup:abort_worker(OldPid), _ = emqx_session_router_worker_sup:abort_worker(OldPid),
{ok, Pid, State#{ pmon => Pmon2 }} {ok, Pid, State#{pmon => Pmon2}}
end end
end. end.
@ -284,5 +312,5 @@ lookup_routes(Topic) ->
route_tab() -> route_tab() ->
case emqx_persistent_session:storage_type() of case emqx_persistent_session:storage_type() of
disc -> ?ROUTE_DISC_TAB; disc -> ?ROUTE_DISC_TAB;
ram -> ?ROUTE_RAM_TAB ram -> ?ROUTE_RAM_TAB
end. end.

View File

@ -26,41 +26,43 @@
%% the initialization, the messages are delivered and the worker is %% the initialization, the messages are delivered and the worker is
%% terminated. %% terminated.
-module(emqx_session_router_worker). -module(emqx_session_router_worker).
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([ buffer/3 -export([
, pendings/1 buffer/3,
, resume_end/3 pendings/1,
, start_link/2 resume_end/3,
]). start_link/2
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
]). terminate/2
]).
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
-record(state, { remote_pid :: pid() -record(state, {
, session_id :: binary() remote_pid :: pid(),
, session_tab :: ets:table() session_id :: binary(),
, messages :: ets:table() session_tab :: ets:table(),
, buffering :: boolean() messages :: ets:table(),
}). buffering :: boolean()
}).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
start_link(SessionTab, #{} = Opts) -> start_link(SessionTab, #{} = Opts) ->
gen_server:start_link(?MODULE, Opts#{ session_tab => SessionTab}, []). gen_server:start_link(?MODULE, Opts#{session_tab => SessionTab}, []).
pendings(Pid) -> pendings(Pid) ->
gen_server:call(Pid, pendings). gen_server:call(Pid, pendings).
@ -68,15 +70,19 @@ pendings(Pid) ->
resume_end(RemotePid, Pid, _SessionID) -> resume_end(RemotePid, Pid, _SessionID) ->
case gen_server:call(Pid, {resume_end, RemotePid}) of case gen_server:call(Pid, {resume_end, RemotePid}) of
{ok, EtsHandle} -> {ok, EtsHandle} ->
?tp(ps_worker_call_ok, #{ pid => Pid ?tp(ps_worker_call_ok, #{
, remote_pid => RemotePid pid => Pid,
, sid => _SessionID}), remote_pid => RemotePid,
sid => _SessionID
}),
{ok, ets:tab2list(EtsHandle)}; {ok, ets:tab2list(EtsHandle)};
{error, _} = Err -> {error, _} = Err ->
?tp(ps_worker_call_failed, #{ pid => Pid ?tp(ps_worker_call_failed, #{
, remote_pid => RemotePid pid => Pid,
, sid => _SessionID remote_pid => RemotePid,
, reason => Err}), sid => _SessionID,
reason => Err
}),
Err Err
end. end.
@ -88,26 +94,31 @@ buffer(Worker, STopic, Msg) ->
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
init(#{ remote_pid := RemotePid init(#{
, session_id := SessionID remote_pid := RemotePid,
, session_tab := SessionTab}) -> session_id := SessionID,
session_tab := SessionTab
}) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
erlang:monitor(process, RemotePid), erlang:monitor(process, RemotePid),
?tp(ps_worker_started, #{ remote_pid => RemotePid ?tp(ps_worker_started, #{
, sid => SessionID }), remote_pid => RemotePid,
{ok, #state{ remote_pid = RemotePid sid => SessionID
, session_id = SessionID }),
, session_tab = SessionTab {ok, #state{
, messages = ets:new(?MODULE, [protected, ordered_set]) remote_pid = RemotePid,
, buffering = true session_id = SessionID,
}}. session_tab = SessionTab,
messages = ets:new(?MODULE, [protected, ordered_set]),
buffering = true
}}.
handle_call(pendings, _From, State) -> handle_call(pendings, _From, State) ->
%% Debug API %% Debug API
{reply, {State#state.messages, State#state.remote_pid}, State}; {reply, {State#state.messages, State#state.remote_pid}, State};
handle_call({resume_end, RemotePid}, _From, #state{remote_pid = RemotePid} = State) -> handle_call({resume_end, RemotePid}, _From, #state{remote_pid = RemotePid} = State) ->
?tp(ps_worker_resume_end, #{sid => State#state.session_id}), ?tp(ps_worker_resume_end, #{sid => State#state.session_id}),
{reply, {ok, State#state.messages}, State#state{ buffering = false }}; {reply, {ok, State#state.messages}, State#state{buffering = false}};
handle_call({resume_end, _RemotePid}, _From, State) -> handle_call({resume_end, _RemotePid}, _From, State) ->
?tp(ps_worker_resume_end_error, #{sid => State#state.session_id}), ?tp(ps_worker_resume_end_error, #{sid => State#state.session_id}),
{reply, {error, wrong_remote_pid}, State}; {reply, {error, wrong_remote_pid}, State};
@ -119,26 +130,30 @@ handle_cast(_Request, State) ->
{noreply, State}. {noreply, State}.
handle_info({buffer, _STopic, _Msg}, State) when not State#state.buffering -> handle_info({buffer, _STopic, _Msg}, State) when not State#state.buffering ->
?tp(ps_worker_drop_deliver, #{ sid => State#state.session_id ?tp(ps_worker_drop_deliver, #{
, msg_id => emqx_message:id(_Msg) sid => State#state.session_id,
}), msg_id => emqx_message:id(_Msg)
}),
{noreply, State}; {noreply, State};
handle_info({buffer, STopic, Msg}, State) when State#state.buffering -> handle_info({buffer, STopic, Msg}, State) when State#state.buffering ->
?tp(ps_worker_deliver, #{ sid => State#state.session_id ?tp(ps_worker_deliver, #{
, msg_id => emqx_message:id(Msg) sid => State#state.session_id,
}), msg_id => emqx_message:id(Msg)
}),
ets:insert(State#state.messages, {{Msg, STopic}}), ets:insert(State#state.messages, {{Msg, STopic}}),
{noreply, State}; {noreply, State};
handle_info({'DOWN', _, process, RemotePid, _Reason}, #state{remote_pid = RemotePid} = State) -> handle_info({'DOWN', _, process, RemotePid, _Reason}, #state{remote_pid = RemotePid} = State) ->
?tp(warning, ps_worker, #{ event => worker_remote_died ?tp(warning, ps_worker, #{
, sid => State#state.session_id event => worker_remote_died,
, msg => "Remote pid died. Exiting." }), sid => State#state.session_id,
msg => "Remote pid died. Exiting."
}),
{stop, normal, State}; {stop, normal, State};
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
terminate(shutdown, _State) -> terminate(shutdown, _State) ->
?tp(ps_worker_shutdown, #{ sid => _State#state.session_id }), ?tp(ps_worker_shutdown, #{sid => _State#state.session_id}),
ok; ok;
terminate(_, _State) -> terminate(_, _State) ->
ok. ok.

View File

@ -18,22 +18,25 @@
-behaviour(supervisor). -behaviour(supervisor).
-export([ start_link/1 -export([start_link/1]).
]).
-export([ abort_worker/1 -export([
, start_worker/2 abort_worker/1,
]). start_worker/2
]).
-export([ init/1 -export([init/1]).
]).
start_link(SessionTab) -> start_link(SessionTab) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, SessionTab). supervisor:start_link({local, ?MODULE}, ?MODULE, SessionTab).
start_worker(SessionID, RemotePid) -> start_worker(SessionID, RemotePid) ->
supervisor:start_child(?MODULE, [#{ session_id => SessionID supervisor:start_child(?MODULE, [
, remote_pid => RemotePid}]). #{
session_id => SessionID,
remote_pid => RemotePid
}
]).
abort_worker(Pid) -> abort_worker(Pid) ->
supervisor:terminate_child(?MODULE, Pid). supervisor:terminate_child(?MODULE, Pid).
@ -44,14 +47,18 @@ abort_worker(Pid) ->
init(SessionTab) -> init(SessionTab) ->
%% Resume worker %% Resume worker
Worker = #{id => session_router_worker, Worker = #{
start => {emqx_session_router_worker, start_link, [SessionTab]}, id => session_router_worker,
restart => transient, start => {emqx_session_router_worker, start_link, [SessionTab]},
shutdown => 2000, restart => transient,
type => worker, shutdown => 2000,
modules => [emqx_session_router_worker]}, type => worker,
Spec = #{ strategy => simple_one_for_one modules => [emqx_session_router_worker]
, intensity => 1 },
, period => 5}, Spec = #{
strategy => simple_one_for_one,
intensity => 1,
period => 5
},
{ok, {Spec, [Worker]}}. {ok, {Spec, [Worker]}}.

View File

@ -23,7 +23,6 @@
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
%% Mnesia bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
@ -32,38 +31,43 @@
%% APIs %% APIs
-export([start_link/0]). -export([start_link/0]).
-export([ subscribe/3 -export([
, unsubscribe/3 subscribe/3,
]). unsubscribe/3
]).
-export([dispatch/3]). -export([dispatch/3]).
-export([ maybe_ack/1 -export([
, maybe_nack_dropped/1 maybe_ack/1,
, nack_no_connection/1 maybe_nack_dropped/1,
, is_ack_required/1 nack_no_connection/1,
]). is_ack_required/1
]).
%% for testing %% for testing
-export([subscribers/2]). -export([subscribers/2]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-export_type([strategy/0]). -export_type([strategy/0]).
-type strategy() :: random -type strategy() ::
| round_robin random
| sticky | round_robin
| hash %% same as hash_clientid, backward compatible | sticky
| hash_clientid %% same as hash_clientid, backward compatible
| hash_topic. | hash
| hash_clientid
| hash_topic.
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-define(TAB, emqx_shared_subscription). -define(TAB, emqx_shared_subscription).
@ -85,33 +89,34 @@
mnesia(boot) -> mnesia(boot) ->
ok = mria:create_table(?TAB, [ ok = mria:create_table(?TAB, [
{type, bag}, {type, bag},
{rlog_shard, ?SHARED_SUB_SHARD}, {rlog_shard, ?SHARED_SUB_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, emqx_shared_subscription}, {record_name, emqx_shared_subscription},
{attributes, record_info(fields, emqx_shared_subscription)}]). {attributes, record_info(fields, emqx_shared_subscription)}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(subscribe(emqx_types:group(), emqx_types:topic(), pid()) -> ok). -spec subscribe(emqx_types:group(), emqx_types:topic(), pid()) -> ok.
subscribe(Group, Topic, SubPid) when is_pid(SubPid) -> subscribe(Group, Topic, SubPid) when is_pid(SubPid) ->
gen_server:call(?SERVER, {subscribe, Group, Topic, SubPid}). gen_server:call(?SERVER, {subscribe, Group, Topic, SubPid}).
-spec(unsubscribe(emqx_types:group(), emqx_types:topic(), pid()) -> ok). -spec unsubscribe(emqx_types:group(), emqx_types:topic(), pid()) -> ok.
unsubscribe(Group, Topic, SubPid) when is_pid(SubPid) -> unsubscribe(Group, Topic, SubPid) when is_pid(SubPid) ->
gen_server:call(?SERVER, {unsubscribe, Group, Topic, SubPid}). gen_server:call(?SERVER, {unsubscribe, Group, Topic, SubPid}).
record(Group, Topic, SubPid) -> record(Group, Topic, SubPid) ->
#emqx_shared_subscription{group = Group, topic = Topic, subpid = SubPid}. #emqx_shared_subscription{group = Group, topic = Topic, subpid = SubPid}.
-spec(dispatch(emqx_types:group(), emqx_types:topic(), emqx_types:delivery()) -spec dispatch(emqx_types:group(), emqx_types:topic(), emqx_types:delivery()) ->
-> emqx_types:deliver_result()). emqx_types:deliver_result().
dispatch(Group, Topic, Delivery) -> dispatch(Group, Topic, Delivery) ->
dispatch(Group, Topic, Delivery, _FailedSubs = []). dispatch(Group, Topic, Delivery, _FailedSubs = []).
@ -122,18 +127,19 @@ dispatch(Group, Topic, Delivery = #delivery{message = Msg}, FailedSubs) ->
{error, no_subscribers}; {error, no_subscribers};
{Type, SubPid} -> {Type, SubPid} ->
case do_dispatch(SubPid, Topic, Msg, Type) of case do_dispatch(SubPid, Topic, Msg, Type) of
ok -> {ok, 1}; ok ->
{ok, 1};
{error, _Reason} -> {error, _Reason} ->
%% Failed to dispatch to this sub, try next. %% Failed to dispatch to this sub, try next.
dispatch(Group, Topic, Delivery, [SubPid | FailedSubs]) dispatch(Group, Topic, Delivery, [SubPid | FailedSubs])
end end
end. end.
-spec(strategy() -> strategy()). -spec strategy() -> strategy().
strategy() -> strategy() ->
emqx:get_config([broker, shared_subscription_strategy]). emqx:get_config([broker, shared_subscription_strategy]).
-spec(ack_enabled() -> boolean()). -spec ack_enabled() -> boolean().
ack_enabled() -> ack_enabled() ->
emqx:get_config([broker, shared_dispatch_ack_enabled]). emqx:get_config([broker, shared_dispatch_ack_enabled]).
@ -167,10 +173,11 @@ dispatch_with_ack(SubPid, Topic, Msg) ->
Ref = erlang:monitor(process, SubPid), Ref = erlang:monitor(process, SubPid),
Sender = self(), Sender = self(),
_ = erlang:send(SubPid, {deliver, Topic, with_ack_ref(Msg, {Sender, Ref})}), _ = erlang:send(SubPid, {deliver, Topic, with_ack_ref(Msg, {Sender, Ref})}),
Timeout = case Msg#message.qos of Timeout =
?QOS_1 -> timer:seconds(?SHARED_SUB_QOS1_DISPATCH_TIMEOUT_SECONDS); case Msg#message.qos of
?QOS_2 -> infinity ?QOS_1 -> timer:seconds(?SHARED_SUB_QOS1_DISPATCH_TIMEOUT_SECONDS);
end, ?QOS_2 -> infinity
end,
try try
receive receive
{Ref, ?ACK} -> {Ref, ?ACK} ->
@ -180,9 +187,8 @@ dispatch_with_ack(SubPid, Topic, Msg) ->
{error, Reason}; {error, Reason};
{'DOWN', Ref, process, SubPid, Reason} -> {'DOWN', Ref, process, SubPid, Reason} ->
{error, Reason} {error, Reason}
after after Timeout ->
Timeout -> {error, timeout}
{error, timeout}
end end
after after
_ = erlang:demonitor(Ref, [flush]) _ = erlang:demonitor(Ref, [flush])
@ -197,11 +203,11 @@ without_ack_ref(Msg) ->
get_ack_ref(Msg) -> get_ack_ref(Msg) ->
emqx_message:get_header(shared_dispatch_ack, Msg, ?NO_ACK). emqx_message:get_header(shared_dispatch_ack, Msg, ?NO_ACK).
-spec(is_ack_required(emqx_types:message()) -> boolean()). -spec is_ack_required(emqx_types:message()) -> boolean().
is_ack_required(Msg) -> ?NO_ACK =/= get_ack_ref(Msg). is_ack_required(Msg) -> ?NO_ACK =/= get_ack_ref(Msg).
%% @doc Negative ack dropped message due to inflight window or message queue being full. %% @doc Negative ack dropped message due to inflight window or message queue being full.
-spec(maybe_nack_dropped(emqx_types:message()) -> ok). -spec maybe_nack_dropped(emqx_types:message()) -> ok.
maybe_nack_dropped(Msg) -> maybe_nack_dropped(Msg) ->
case get_ack_ref(Msg) of case get_ack_ref(Msg) of
?NO_ACK -> ok; ?NO_ACK -> ok;
@ -211,17 +217,17 @@ maybe_nack_dropped(Msg) ->
%% @doc Negative ack message due to connection down. %% @doc Negative ack message due to connection down.
%% Assuming this function is always called when ack is required %% Assuming this function is always called when ack is required
%% i.e is_ack_required returned true. %% i.e is_ack_required returned true.
-spec(nack_no_connection(emqx_types:message()) -> ok). -spec nack_no_connection(emqx_types:message()) -> ok.
nack_no_connection(Msg) -> nack_no_connection(Msg) ->
{Sender, Ref} = get_ack_ref(Msg), {Sender, Ref} = get_ack_ref(Msg),
nack(Sender, Ref, no_connection). nack(Sender, Ref, no_connection).
-spec(nack(pid(), reference(), dropped | no_connection) -> ok). -spec nack(pid(), reference(), dropped | no_connection) -> ok.
nack(Sender, Ref, Reason) -> nack(Sender, Ref, Reason) ->
erlang:send(Sender, {Ref, ?NACK(Reason)}), erlang:send(Sender, {Ref, ?NACK(Reason)}),
ok. ok.
-spec(maybe_ack(emqx_types:message()) -> emqx_types:message()). -spec maybe_ack(emqx_types:message()) -> emqx_types:message().
maybe_ack(Msg) -> maybe_ack(Msg) ->
case get_ack_ref(Msg) of case get_ack_ref(Msg) of
?NO_ACK -> ?NO_ACK ->
@ -262,7 +268,8 @@ do_pick(Strategy, ClientId, SourceTopic, Group, Topic, FailedSubs) ->
{fresh, pick_subscriber(Group, Topic, Strategy, ClientId, SourceTopic, Subs)} {fresh, pick_subscriber(Group, Topic, Strategy, ClientId, SourceTopic, Subs)}
end. end.
pick_subscriber(_Group, _Topic, _Strategy, _ClientId, _SourceTopic, [Sub]) -> Sub; pick_subscriber(_Group, _Topic, _Strategy, _ClientId, _SourceTopic, [Sub]) ->
Sub;
pick_subscriber(Group, Topic, Strategy, ClientId, SourceTopic, Subs) -> pick_subscriber(Group, Topic, Strategy, ClientId, SourceTopic, Subs) ->
Nth = do_pick_subscriber(Group, Topic, Strategy, ClientId, SourceTopic, length(Subs)), Nth = do_pick_subscriber(Group, Topic, Strategy, ClientId, SourceTopic, length(Subs)),
lists:nth(Nth, Subs). lists:nth(Nth, Subs).
@ -277,10 +284,11 @@ do_pick_subscriber(_Group, _Topic, hash_clientid, ClientId, _SourceTopic, Count)
do_pick_subscriber(_Group, _Topic, hash_topic, _ClientId, SourceTopic, Count) -> do_pick_subscriber(_Group, _Topic, hash_topic, _ClientId, SourceTopic, Count) ->
1 + erlang:phash2(SourceTopic) rem Count; 1 + erlang:phash2(SourceTopic) rem Count;
do_pick_subscriber(Group, Topic, round_robin, _ClientId, _SourceTopic, Count) -> do_pick_subscriber(Group, Topic, round_robin, _ClientId, _SourceTopic, Count) ->
Rem = case erlang:get({shared_sub_round_robin, Group, Topic}) of Rem =
undefined -> rand:uniform(Count) - 1; case erlang:get({shared_sub_round_robin, Group, Topic}) of
N -> (N + 1) rem Count undefined -> rand:uniform(Count) - 1;
end, N -> (N + 1) rem Count
end,
_ = erlang:put({shared_sub_round_robin, Group, Topic}, Rem), _ = erlang:put({shared_sub_round_robin, Group, Topic}, Rem),
Rem + 1. Rem + 1.
@ -301,26 +309,27 @@ init([]) ->
init_monitors() -> init_monitors() ->
mnesia:foldl( mnesia:foldl(
fun(#emqx_shared_subscription{subpid = SubPid}, Mon) -> fun(#emqx_shared_subscription{subpid = SubPid}, Mon) ->
emqx_pmon:monitor(SubPid, Mon) emqx_pmon:monitor(SubPid, Mon)
end, emqx_pmon:new(), ?TAB). end,
emqx_pmon:new(),
?TAB
).
handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon}) -> handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon}) ->
mria:dirty_write(?TAB, record(Group, Topic, SubPid)), mria:dirty_write(?TAB, record(Group, Topic, SubPid)),
case ets:member(?SHARED_SUBS, {Group, Topic}) of case ets:member(?SHARED_SUBS, {Group, Topic}) of
true -> ok; true -> ok;
false -> ok = emqx_router:do_add_route(Topic, {Group, node()}) false -> ok = emqx_router:do_add_route(Topic, {Group, node()})
end, end,
ok = maybe_insert_alive_tab(SubPid), ok = maybe_insert_alive_tab(SubPid),
true = ets:insert(?SHARED_SUBS, {{Group, Topic}, SubPid}), true = ets:insert(?SHARED_SUBS, {{Group, Topic}, SubPid}),
{reply, ok, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})}; {reply, ok, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) -> handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) ->
mria:dirty_delete_object(?TAB, record(Group, Topic, SubPid)), mria:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}), true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
delete_route_if_needed({Group, Topic}), delete_route_if_needed({Group, Topic}),
{reply, ok, State}; {reply, ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", req => Req}), ?SLOG(error, #{msg => "unexpected_call", req => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -332,7 +341,6 @@ handle_cast(Msg, State) ->
handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) -> handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) ->
#emqx_shared_subscription{subpid = SubPid} = NewRecord, #emqx_shared_subscription{subpid = SubPid} = NewRecord,
{noreply, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})}; {noreply, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
%% The subscriber may have subscribed multiple topics, so we need to keep monitoring the PID until %% The subscriber may have subscribed multiple topics, so we need to keep monitoring the PID until
%% it `unsubscribed` the last topic. %% it `unsubscribed` the last topic.
%% The trick is we don't demonitor the subscriber here, and (after a long time) it will eventually %% The trick is we don't demonitor the subscriber here, and (after a long time) it will eventually
@ -343,12 +351,10 @@ handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = P
handle_info({mnesia_table_event, _Event}, State) -> handle_info({mnesia_table_event, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info({'DOWN', _MRef, process, SubPid, Reason}, State = #state{pmon = PMon}) -> handle_info({'DOWN', _MRef, process, SubPid, Reason}, State = #state{pmon = PMon}) ->
?SLOG(info, #{msg => "shared_subscriber_down", sub_pid => SubPid, reason => Reason}), ?SLOG(info, #{msg => "shared_subscriber_down", sub_pid => SubPid, reason => Reason}),
cleanup_down(SubPid), cleanup_down(SubPid),
{noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})}; {noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})};
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
@ -364,7 +370,9 @@ code_change(_OldVsn, State, _Extra) ->
%% keep track of alive remote pids %% keep track of alive remote pids
maybe_insert_alive_tab(Pid) when ?IS_LOCAL_PID(Pid) -> ok; maybe_insert_alive_tab(Pid) when ?IS_LOCAL_PID(Pid) -> ok;
maybe_insert_alive_tab(Pid) when is_pid(Pid) -> ets:insert(?ALIVE_SUBS, {Pid}), ok. maybe_insert_alive_tab(Pid) when is_pid(Pid) ->
ets:insert(?ALIVE_SUBS, {Pid}),
ok.
cleanup_down(SubPid) -> cleanup_down(SubPid) ->
?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid), ?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid),
@ -373,13 +381,16 @@ cleanup_down(SubPid) ->
ok = mria:dirty_delete_object(?TAB, Record), ok = mria:dirty_delete_object(?TAB, Record),
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}), true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
delete_route_if_needed({Group, Topic}) delete_route_if_needed({Group, Topic})
end, mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})). end,
mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})
).
update_stats(State) -> update_stats(State) ->
emqx_stats:setstat('subscriptions.shared.count', emqx_stats:setstat(
'subscriptions.shared.max', 'subscriptions.shared.count',
ets:info(?TAB, size) 'subscriptions.shared.max',
), ets:info(?TAB, size)
),
State. State.
%% Return 'true' if the subscriber process is alive AND not in the failed list %% Return 'true' if the subscriber process is alive AND not in the failed list

View File

@ -23,123 +23,140 @@
-include("types.hrl"). -include("types.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
%% APIs %% APIs
-export([ start_link/0 -export([
, start_link/1 start_link/0,
, stop/0 start_link/1,
]). stop/0
]).
%% Stats API. %% Stats API.
-export([ getstats/0 -export([
, getstat/1 getstats/0,
, setstat/2 getstat/1,
, setstat/3 setstat/2,
, statsfun/1 setstat/3,
, statsfun/2 statsfun/1,
]). statsfun/2
]).
-export([ update_interval/2 -export([
, update_interval/3 update_interval/2,
, cancel_update/1 update_interval/3,
]). cancel_update/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-export_type([stats/0]). -export_type([stats/0]).
-record(update, {name, countdown, interval, func}). -record(update, {name, countdown, interval, func}).
-record(state, { -record(state, {
timer :: maybe(reference()), timer :: maybe(reference()),
updates :: [#update{}], updates :: [#update{}],
tick_ms :: timeout() tick_ms :: timeout()
}). }).
-type(stats() :: list({atom(), non_neg_integer()})). -type stats() :: list({atom(), non_neg_integer()}).
%% Connection stats %% Connection stats
-define(CONNECTION_STATS, -define(CONNECTION_STATS,
[ 'connections.count' %% Count of Concurrent Connections %% Count of Concurrent Connections
, 'connections.max' %% Maximum Number of Concurrent Connections [
, 'live_connections.count' %% Count of connected clients 'connections.count',
, 'live_connections.max' %% Maximum number of connected clients %% Maximum Number of Concurrent Connections
]). 'connections.max',
%% Count of connected clients
'live_connections.count',
%% Maximum number of connected clients
'live_connections.max'
]
).
%% Channel stats %% Channel stats
-define(CHANNEL_STATS, -define(CHANNEL_STATS,
['channels.count', %% Count of Concurrent Channels %% Count of Concurrent Channels
'channels.max' %% Maximum Number of Concurrent Channels [
]). 'channels.count',
%% Maximum Number of Concurrent Channels
'channels.max'
]
).
%% Session stats %% Session stats
-define(SESSION_STATS, -define(SESSION_STATS,
['sessions.count', %% Count of Concurrent Sessions %% Count of Concurrent Sessions
'sessions.max' %% Maximum Number of Concurrent Sessions [
]). 'sessions.count',
%% Maximum Number of Concurrent Sessions
'sessions.max'
]
).
%% PubSub stats %% PubSub stats
-define(PUBSUB_STATS, -define(PUBSUB_STATS, [
['topics.count', 'topics.count',
'topics.max', 'topics.max',
'suboptions.count', 'suboptions.count',
'suboptions.max', 'suboptions.max',
'subscribers.count', 'subscribers.count',
'subscribers.max', 'subscribers.max',
'subscriptions.count', 'subscriptions.count',
'subscriptions.max', 'subscriptions.max',
'subscriptions.shared.count', 'subscriptions.shared.count',
'subscriptions.shared.max' 'subscriptions.shared.max'
]). ]).
%% Route stats %% Route stats
-define(ROUTE_STATS, -define(ROUTE_STATS, [
['routes.count', 'routes.count',
'routes.max' 'routes.max'
]). ]).
%% Retained stats %% Retained stats
-define(RETAINED_STATS, -define(RETAINED_STATS, [
['retained.count', 'retained.count',
'retained.max' 'retained.max'
]). ]).
-define(TAB, ?MODULE). -define(TAB, ?MODULE).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-type(opts() :: #{tick_ms := timeout()}). -type opts() :: #{tick_ms := timeout()}.
%% @doc Start stats server %% @doc Start stats server
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
start_link(#{tick_ms => timer:seconds(1)}). start_link(#{tick_ms => timer:seconds(1)}).
-spec(start_link(opts()) -> startlink_ret()). -spec start_link(opts()) -> startlink_ret().
start_link(Opts) -> start_link(Opts) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, Opts, []). gen_server:start_link({local, ?SERVER}, ?MODULE, Opts, []).
-spec(stop() -> ok). -spec stop() -> ok.
stop() -> stop() ->
gen_server:call(?SERVER, stop, infinity). gen_server:call(?SERVER, stop, infinity).
%% @doc Generate stats fun. %% @doc Generate stats fun.
-spec(statsfun(Stat :: atom()) -> fun()). -spec statsfun(Stat :: atom()) -> fun().
statsfun(Stat) -> statsfun(Stat) ->
fun(Val) -> setstat(Stat, Val) end. fun(Val) -> setstat(Stat, Val) end.
-spec(statsfun(Stat :: atom(), MaxStat :: atom()) -> fun()). -spec statsfun(Stat :: atom(), MaxStat :: atom()) -> fun().
statsfun(Stat, MaxStat) -> statsfun(Stat, MaxStat) ->
fun(Val) -> setstat(Stat, MaxStat, Val) end. fun(Val) -> setstat(Stat, MaxStat, Val) end.
%% @doc Get all statistics. %% @doc Get all statistics.
-spec(getstats() -> stats()). -spec getstats() -> stats().
getstats() -> getstats() ->
case ets:info(?TAB, name) of case ets:info(?TAB, name) of
undefined -> []; undefined -> [];
@ -147,7 +164,7 @@ getstats() ->
end. end.
%% @doc Get stats by name. %% @doc Get stats by name.
-spec(getstat(atom()) -> maybe(non_neg_integer())). -spec getstat(atom()) -> maybe(non_neg_integer()).
getstat(Name) -> getstat(Name) ->
case ets:lookup(?TAB, Name) of case ets:lookup(?TAB, Name) of
[{Name, Val}] -> Val; [{Name, Val}] -> Val;
@ -155,25 +172,28 @@ getstat(Name) ->
end. end.
%% @doc Set stats %% @doc Set stats
-spec(setstat(Stat :: atom(), Val :: pos_integer()) -> boolean()). -spec setstat(Stat :: atom(), Val :: pos_integer()) -> boolean().
setstat(Stat, Val) when is_integer(Val) -> setstat(Stat, Val) when is_integer(Val) ->
safe_update_element(Stat, Val). safe_update_element(Stat, Val).
%% @doc Set stats with max value. %% @doc Set stats with max value.
-spec(setstat(Stat :: atom(), MaxStat :: atom(), -spec setstat(
Val :: pos_integer()) -> ok). Stat :: atom(),
MaxStat :: atom(),
Val :: pos_integer()
) -> ok.
setstat(Stat, MaxStat, Val) when is_integer(Val) -> setstat(Stat, MaxStat, Val) when is_integer(Val) ->
cast({setstat, Stat, MaxStat, Val}). cast({setstat, Stat, MaxStat, Val}).
-spec(update_interval(atom(), fun()) -> ok). -spec update_interval(atom(), fun()) -> ok.
update_interval(Name, UpFun) -> update_interval(Name, UpFun) ->
update_interval(Name, 1, UpFun). update_interval(Name, 1, UpFun).
-spec(update_interval(atom(), pos_integer(), fun()) -> ok). -spec update_interval(atom(), pos_integer(), fun()) -> ok.
update_interval(Name, Secs, UpFun) when is_integer(Secs), Secs >= 1 -> update_interval(Name, Secs, UpFun) when is_integer(Secs), Secs >= 1 ->
cast({update_interval, rec(Name, Secs, UpFun)}). cast({update_interval, rec(Name, Secs, UpFun)}).
-spec(cancel_update(atom()) -> ok). -spec cancel_update(atom()) -> ok.
cancel_update(Name) -> cancel_update(Name) ->
cast({cancel_update, Name}). cast({cancel_update, Name}).
@ -188,13 +208,14 @@ cast(Msg) -> gen_server:cast(?SERVER, Msg).
init(#{tick_ms := TickMs}) -> init(#{tick_ms := TickMs}) ->
ok = emqx_tables:new(?TAB, [public, set, {write_concurrency, true}]), ok = emqx_tables:new(?TAB, [public, set, {write_concurrency, true}]),
Stats = lists:append([?CONNECTION_STATS, Stats = lists:append([
?CHANNEL_STATS, ?CONNECTION_STATS,
?SESSION_STATS, ?CHANNEL_STATS,
?PUBSUB_STATS, ?SESSION_STATS,
?ROUTE_STATS, ?PUBSUB_STATS,
?RETAINED_STATS ?ROUTE_STATS,
]), ?RETAINED_STATS
]),
true = ets:insert(?TAB, [{Name, 0} || Name <- Stats]), true = ets:insert(?TAB, [{Name, 0} || Name <- Stats]),
{ok, start_timer(#state{updates = [], tick_ms = TickMs}), hibernate}. {ok, start_timer(#state{updates = [], tick_ms = TickMs}), hibernate}.
@ -203,7 +224,6 @@ start_timer(#state{tick_ms = Ms} = State) ->
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
{stop, normal, ok, State}; {stop, normal, ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, State}. {reply, ignored, State}.
@ -212,59 +232,77 @@ handle_cast({setstat, Stat, MaxStat, Val}, State) ->
try ets:lookup_element(?TAB, MaxStat, 2) of try ets:lookup_element(?TAB, MaxStat, 2) of
MaxVal when Val > MaxVal -> MaxVal when Val > MaxVal ->
ets:update_element(?TAB, MaxStat, {2, Val}); ets:update_element(?TAB, MaxStat, {2, Val});
_ -> ok _ ->
ok
catch catch
error:badarg -> error:badarg ->
ets:insert(?TAB, {MaxStat, Val}) ets:insert(?TAB, {MaxStat, Val})
end, end,
safe_update_element(Stat, Val), safe_update_element(Stat, Val),
?tp(emqx_stats_setstat, ?tp(
#{ count_stat => Stat emqx_stats_setstat,
, max_stat => MaxStat #{
, value => Val count_stat => Stat,
}), max_stat => MaxStat,
value => Val
}
),
{noreply, State}; {noreply, State};
handle_cast(
handle_cast({update_interval, Update = #update{name = Name}}, {update_interval, Update = #update{name = Name}},
State = #state{updates = Updates}) -> State = #state{updates = Updates}
NState = case lists:keyfind(Name, #update.name, Updates) of ) ->
#update{} -> NState =
?SLOG(warning, #{msg => "duplicated_update", case lists:keyfind(Name, #update.name, Updates) of
name => Name #update{} ->
}), ?SLOG(warning, #{
State; msg => "duplicated_update",
false -> State#state{updates = [Update | Updates]} name => Name
end, }),
State;
false ->
State#state{updates = [Update | Updates]}
end,
{noreply, NState}; {noreply, NState};
handle_cast({cancel_update, Name}, State = #state{updates = Updates}) -> handle_cast({cancel_update, Name}, State = #state{updates = Updates}) ->
Updates1 = lists:keydelete(Name, #update.name, Updates), Updates1 = lists:keydelete(Name, #update.name, Updates),
{noreply, State#state{updates = Updates1}}; {noreply, State#state{updates = Updates1}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
{noreply, State}. {noreply, State}.
handle_info({timeout, TRef, tick}, State = #state{timer = TRef, updates = Updates}) -> handle_info({timeout, TRef, tick}, State = #state{timer = TRef, updates = Updates}) ->
Updates1 = lists:foldl( Updates1 = lists:foldl(
fun(Update = #update{name = Name, countdown = C, interval = I, fun
func = UpFun}, Acc) when C =< 0 -> (
try UpFun() Update = #update{
catch name = Name,
Error : Reason : Stacktrace -> countdown = C,
?SLOG(error, #{msg => "update_name_failed", interval = I,
name => Name, func = UpFun
exception => Error, },
reason => Reason, Acc
stacktrace => Stacktrace ) when C =< 0 ->
}) try
end, UpFun()
[Update#update{countdown = I} | Acc]; catch
(Update = #update{countdown = C}, Acc) -> Error:Reason:Stacktrace ->
[Update#update{countdown = C - 1} | Acc] ?SLOG(error, #{
end, [], Updates), msg => "update_name_failed",
name => Name,
exception => Error,
reason => Reason,
stacktrace => Stacktrace
})
end,
[Update#update{countdown = I} | Acc];
(Update = #update{countdown = C}, Acc) ->
[Update#update{countdown = C - 1} | Acc]
end,
[],
Updates
),
{noreply, start_timer(State#state{updates = Updates1}), hibernate}; {noreply, start_timer(State#state{updates = Updates1}), hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -283,7 +321,8 @@ safe_update_element(Key, Val) ->
try ets:update_element(?TAB, Key, {2, Val}) of try ets:update_element(?TAB, Key, {2, Val}) of
false -> false ->
ets:insert_new(?TAB, {Key, Val}); ets:insert_new(?TAB, {Key, Val});
true -> true true ->
true
catch catch
error:badarg -> error:badarg ->
?SLOG(warning, #{ ?SLOG(warning, #{

View File

@ -20,17 +20,19 @@
-include("types.hrl"). -include("types.hrl").
-export([ start_link/0 -export([
, start_child/1 start_link/0,
, start_child/2 start_child/1,
, stop_child/1 start_child/2,
]). stop_child/1
]).
-export([init/1]). -export([init/1]).
-type(startchild_ret() :: {ok, supervisor:child()} -type startchild_ret() ::
| {ok, supervisor:child(), term()} {ok, supervisor:child()}
| {error, term()}). | {ok, supervisor:child(), term()}
| {error, term()}.
-define(SUP, ?MODULE). -define(SUP, ?MODULE).
@ -38,19 +40,19 @@
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
supervisor:start_link({local, ?SUP}, ?MODULE, []). supervisor:start_link({local, ?SUP}, ?MODULE, []).
-spec(start_child(supervisor:child_spec()) -> startchild_ret()). -spec start_child(supervisor:child_spec()) -> startchild_ret().
start_child(ChildSpec) when is_map(ChildSpec) -> start_child(ChildSpec) when is_map(ChildSpec) ->
supervisor:start_child(?SUP, ChildSpec). supervisor:start_child(?SUP, ChildSpec).
-spec(start_child(module(), worker | supervisor) -> startchild_ret()). -spec start_child(module(), worker | supervisor) -> startchild_ret().
start_child(Mod, Type) -> start_child(Mod, Type) ->
start_child(child_spec(Mod, Type)). start_child(child_spec(Mod, Type)).
-spec(stop_child(supervisor:child_id()) -> ok | {error, term()}). -spec stop_child(supervisor:child_id()) -> ok | {error, term()}.
stop_child(ChildId) -> stop_child(ChildId) ->
case supervisor:terminate_child(?SUP, ChildId) of case supervisor:terminate_child(?SUP, ChildId) of
ok -> supervisor:delete_child(?SUP, ChildId); ok -> supervisor:delete_child(?SUP, ChildId);
@ -69,16 +71,18 @@ init([]) ->
CMSup = child_spec(emqx_cm_sup, supervisor), CMSup = child_spec(emqx_cm_sup, supervisor),
SysSup = child_spec(emqx_sys_sup, supervisor), SysSup = child_spec(emqx_sys_sup, supervisor),
Limiter = child_spec(emqx_limiter_sup, supervisor), Limiter = child_spec(emqx_limiter_sup, supervisor),
Children = [KernelSup] ++ Children =
[SessionSup || emqx_persistent_session:is_store_enabled()] ++ [KernelSup] ++
[RouterSup || emqx_boot:is_enabled(router)] ++ [SessionSup || emqx_persistent_session:is_store_enabled()] ++
[BrokerSup || emqx_boot:is_enabled(broker)] ++ [RouterSup || emqx_boot:is_enabled(router)] ++
[CMSup || emqx_boot:is_enabled(broker)] ++ [BrokerSup || emqx_boot:is_enabled(broker)] ++
[SysSup, Limiter], [CMSup || emqx_boot:is_enabled(broker)] ++
SupFlags = #{strategy => one_for_all, [SysSup, Limiter],
intensity => 0, SupFlags = #{
period => 1 strategy => one_for_all,
}, intensity => 0,
period => 1
},
{ok, {SupFlags, Children}}. {ok, {SupFlags, Children}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -86,20 +90,20 @@ init([]) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
child_spec(Mod, supervisor) -> child_spec(Mod, supervisor) ->
#{id => Mod, #{
start => {Mod, start_link, []}, id => Mod,
restart => permanent, start => {Mod, start_link, []},
shutdown => infinity, restart => permanent,
type => supervisor, shutdown => infinity,
modules => [Mod] type => supervisor,
}; modules => [Mod]
};
child_spec(Mod, worker) -> child_spec(Mod, worker) ->
#{id => Mod, #{
start => {Mod, start_link, []}, id => Mod,
restart => permanent, start => {Mod, start_link, []},
shutdown => 15000, restart => permanent,
type => worker, shutdown => 15000,
modules => [Mod] type => worker,
}. modules => [Mod]
}.

View File

@ -22,32 +22,35 @@
-include("types.hrl"). -include("types.hrl").
-include("logger.hrl"). -include("logger.hrl").
-export([
start_link/0,
stop/0
]).
-export([ start_link/0 -export([
, stop/0 version/0,
]). uptime/0,
datetime/0,
-export([ version/0 sysdescr/0
, uptime/0 ]).
, datetime/0
, sysdescr/0
]).
-export([info/0]). -export([info/0]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
]). terminate/2
]).
-export([ on_client_connected/2 -export([
, on_client_disconnected/3 on_client_connected/2,
, on_client_subscribed/3 on_client_disconnected/3,
, on_client_unsubscribed/3 on_client_subscribed/3,
]). on_client_unsubscribed/3
]).
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
@ -57,27 +60,33 @@
-import(emqx_topic, [systop/1]). -import(emqx_topic, [systop/1]).
-import(emqx_misc, [start_timer/2]). -import(emqx_misc, [start_timer/2]).
-record(state, -record(state, {
{heartbeat :: maybe(reference()) heartbeat :: maybe(reference()),
, ticker :: maybe(reference()) ticker :: maybe(reference()),
, sysdescr :: binary() sysdescr :: binary()
}). }).
-define(APP, emqx). -define(APP, emqx).
-define(SYS, ?MODULE). -define(SYS, ?MODULE).
-define(INFO_KEYS, -define(INFO_KEYS,
[ version % Broker version % Broker version
, uptime % Broker uptime [
, datetime % Broker local datetime version,
, sysdescr % Broker description % Broker uptime
]). uptime,
% Broker local datetime
datetime,
% Broker description
sysdescr
]
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). -spec start_link() -> {ok, pid()} | ignore | {error, any()}.
start_link() -> start_link() ->
gen_server:start_link({local, ?SYS}, ?MODULE, [], []). gen_server:start_link({local, ?SYS}, ?MODULE, [], []).
@ -85,26 +94,28 @@ stop() ->
gen_server:stop(?SYS). gen_server:stop(?SYS).
%% @doc Get sys version %% @doc Get sys version
-spec(version() -> string()). -spec version() -> string().
version() -> emqx_app:get_release(). version() -> emqx_app:get_release().
%% @doc Get sys description %% @doc Get sys description
-spec(sysdescr() -> string()). -spec sysdescr() -> string().
sysdescr() -> emqx_app:get_description(). sysdescr() -> emqx_app:get_description().
%% @doc Get sys uptime %% @doc Get sys uptime
-spec(uptime() -> Milliseconds :: integer()). -spec uptime() -> Milliseconds :: integer().
uptime() -> uptime() ->
{TotalWallClock, _} = erlang:statistics(wall_clock), {TotalWallClock, _} = erlang:statistics(wall_clock),
TotalWallClock. TotalWallClock.
%% @doc Get sys datetime %% @doc Get sys datetime
-spec(datetime() -> string()). -spec datetime() -> string().
datetime() -> datetime() ->
{{Y, M, D}, {H, MM, S}} = calendar:local_time(), {{Y, M, D}, {H, MM, S}} = calendar:local_time(),
lists:flatten( lists:flatten(
io_lib:format( io_lib:format(
"~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S]
)
).
sys_interval() -> sys_interval() ->
emqx:get_config([sys_topics, sys_msg_interval]). emqx:get_config([sys_topics, sys_msg_interval]).
@ -116,19 +127,21 @@ sys_event_messages() ->
emqx:get_config([sys_topics, sys_event_messages]). emqx:get_config([sys_topics, sys_event_messages]).
%% @doc Get sys info %% @doc Get sys info
-spec(info() -> list(tuple())). -spec info() -> list(tuple()).
info() -> info() ->
[{version, version()}, [
{sysdescr, sysdescr()}, {version, version()},
{uptime, uptime()}, {sysdescr, sysdescr()},
{datetime, datetime()}]. {uptime, uptime()},
{datetime, datetime()}
].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
State = #state{sysdescr = iolist_to_binary(sysdescr())}, State = #state{sysdescr = iolist_to_binary(sysdescr())},
load_event_hooks(), load_event_hooks(),
{ok, heartbeat(tick(State))}. {ok, heartbeat(tick(State))}.
@ -139,11 +152,15 @@ tick(State) ->
load_event_hooks() -> load_event_hooks() ->
lists:foreach( lists:foreach(
fun({_, false}) -> ok; fun
({K, true}) -> ({_, false}) ->
{HookPoint, Fun} = hook_and_fun(K), ok;
emqx_hooks:put(HookPoint, {?MODULE, Fun, []}) ({K, true}) ->
end, maps:to_list(sys_event_messages())). {HookPoint, Fun} = hook_and_fun(K),
emqx_hooks:put(HookPoint, {?MODULE, Fun, []})
end,
maps:to_list(sys_event_messages())
).
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
@ -157,7 +174,6 @@ handle_info({timeout, TRef, heartbeat}, State = #state{heartbeat = TRef}) ->
publish_any(uptime, integer_to_binary(uptime())), publish_any(uptime, integer_to_binary(uptime())),
publish_any(datetime, iolist_to_binary(datetime())), publish_any(datetime, iolist_to_binary(datetime())),
{noreply, heartbeat(State)}; {noreply, heartbeat(State)};
handle_info({timeout, TRef, tick}, State = #state{ticker = TRef, sysdescr = Descr}) -> handle_info({timeout, TRef, tick}, State = #state{ticker = TRef, sysdescr = Descr}) ->
publish_any(version, version()), publish_any(version, version()),
publish_any(sysdescr, Descr), publish_any(sysdescr, Descr),
@ -165,7 +181,6 @@ handle_info({timeout, TRef, tick}, State = #state{ticker = TRef, sysdescr = Desc
publish_any(stats, emqx_stats:getstats()), publish_any(stats, emqx_stats:getstats()),
publish_any(metrics, emqx_metrics:all()), publish_any(metrics, emqx_metrics:all()),
{noreply, tick(State), hibernate}; {noreply, tick(State), hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -175,10 +190,13 @@ terminate(_Reason, #state{heartbeat = TRef1, ticker = TRef2}) ->
lists:foreach(fun emqx_misc:cancel_timer/1, [TRef1, TRef2]). lists:foreach(fun emqx_misc:cancel_timer/1, [TRef1, TRef2]).
unload_event_hooks() -> unload_event_hooks() ->
lists:foreach(fun({K, _}) -> lists:foreach(
{HookPoint, Fun} = hook_and_fun(K), fun({K, _}) ->
emqx_hooks:del(HookPoint, {?MODULE, Fun}) {HookPoint, Fun} = hook_and_fun(K),
end, maps:to_list(sys_event_messages())). emqx_hooks:del(HookPoint, {?MODULE, Fun})
end,
maps:to_list(sys_event_messages())
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% hook callbacks %% hook callbacks
@ -187,20 +205,22 @@ unload_event_hooks() ->
on_client_connected(ClientInfo, ConnInfo) -> on_client_connected(ClientInfo, ConnInfo) ->
Payload0 = common_infos(ClientInfo, ConnInfo), Payload0 = common_infos(ClientInfo, ConnInfo),
Payload = Payload0#{ Payload = Payload0#{
keepalive => maps:get(keepalive, ConnInfo, 0), keepalive => maps:get(keepalive, ConnInfo, 0),
clean_start => maps:get(clean_start, ConnInfo, true), clean_start => maps:get(clean_start, ConnInfo, true),
expiry_interval => maps:get(expiry_interval, ConnInfo, 0) expiry_interval => maps:get(expiry_interval, ConnInfo, 0)
}, },
publish(connected, Payload). publish(connected, Payload).
on_client_disconnected(ClientInfo, Reason, on_client_disconnected(
ConnInfo = #{disconnected_at := DisconnectedAt}) -> ClientInfo,
Reason,
ConnInfo = #{disconnected_at := DisconnectedAt}
) ->
Payload0 = common_infos(ClientInfo, ConnInfo), Payload0 = common_infos(ClientInfo, ConnInfo),
Payload = Payload0#{ Payload = Payload0#{
reason => reason(Reason), reason => reason(Reason),
disconnected_at => DisconnectedAt disconnected_at => DisconnectedAt
}, },
publish(disconnected, Payload). publish(disconnected, Payload).
-compile({inline, [reason/1]}). -compile({inline, [reason/1]}).
@ -209,29 +229,41 @@ reason({shutdown, Reason}) when is_atom(Reason) -> Reason;
reason({Error, _}) when is_atom(Error) -> Error; reason({Error, _}) when is_atom(Error) -> Error;
reason(_) -> internal_error. reason(_) -> internal_error.
on_client_subscribed(_ClientInfo = #{clientid := ClientId, on_client_subscribed(
username := Username, _ClientInfo = #{
protocol := Protocol}, clientid := ClientId,
Topic, SubOpts) -> username := Username,
Payload = #{clientid => ClientId, protocol := Protocol
username => Username, },
protocol => Protocol, Topic,
topic => Topic, SubOpts
subopts => SubOpts, ) ->
ts => erlang:system_time(millisecond) Payload = #{
}, clientid => ClientId,
username => Username,
protocol => Protocol,
topic => Topic,
subopts => SubOpts,
ts => erlang:system_time(millisecond)
},
publish(subscribed, Payload). publish(subscribed, Payload).
on_client_unsubscribed(_ClientInfo = #{clientid := ClientId, on_client_unsubscribed(
username := Username, _ClientInfo = #{
protocol := Protocol}, clientid := ClientId,
Topic, _SubOpts) -> username := Username,
Payload = #{clientid => ClientId, protocol := Protocol
username => Username, },
protocol => Protocol, Topic,
topic => Topic, _SubOpts
ts => erlang:system_time(millisecond) ) ->
}, Payload = #{
clientid => ClientId,
username => Username,
protocol => Protocol,
topic => Topic,
ts => erlang:system_time(millisecond)
},
publish(unsubscribed, Payload). publish(unsubscribed, Payload).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -263,13 +295,21 @@ publish(brokers, Nodes) ->
Payload = string:join([atom_to_list(N) || N <- Nodes], ","), Payload = string:join([atom_to_list(N) || N <- Nodes], ","),
safe_publish(<<"$SYS/brokers">>, #{retain => true}, Payload); safe_publish(<<"$SYS/brokers">>, #{retain => true}, Payload);
publish(stats, Stats) -> publish(stats, Stats) ->
[safe_publish(systop(lists:concat(['stats/', Stat])), integer_to_binary(Val)) [
|| {Stat, Val} <- Stats, is_atom(Stat), is_integer(Val)]; safe_publish(systop(lists:concat(['stats/', Stat])), integer_to_binary(Val))
|| {Stat, Val} <- Stats, is_atom(Stat), is_integer(Val)
];
publish(metrics, Metrics) -> publish(metrics, Metrics) ->
[safe_publish(systop(metric_topic(Name)), integer_to_binary(Val)) [
|| {Name, Val} <- Metrics, is_atom(Name), is_integer(Val)]; safe_publish(systop(metric_topic(Name)), integer_to_binary(Val))
publish(Event, Payload) when Event == connected; Event == disconnected; || {Name, Val} <- Metrics, is_atom(Name), is_integer(Val)
Event == subscribed; Event == unsubscribed -> ];
publish(Event, Payload) when
Event == connected;
Event == disconnected;
Event == subscribed;
Event == unsubscribed
->
Topic = event_topic(Event, Payload), Topic = event_topic(Event, Payload),
safe_publish(Topic, emqx_json:encode(Payload)). safe_publish(Topic, emqx_json:encode(Payload)).
@ -280,42 +320,55 @@ safe_publish(Topic, Payload) ->
safe_publish(Topic, #{}, Payload). safe_publish(Topic, #{}, Payload).
safe_publish(Topic, Flags, Payload) -> safe_publish(Topic, Flags, Payload) ->
emqx_broker:safe_publish( emqx_broker:safe_publish(
emqx_message:set_flags( emqx_message:set_flags(
maps:merge(#{sys => true}, Flags), maps:merge(#{sys => true}, Flags),
emqx_message:make(?SYS, Topic, iolist_to_binary(Payload)))). emqx_message:make(?SYS, Topic, iolist_to_binary(Payload))
)
).
common_infos( common_infos(
_ClientInfo = #{clientid := ClientId, _ClientInfo = #{
username := Username, clientid := ClientId,
peerhost := PeerHost, username := Username,
sockport := SockPort, peerhost := PeerHost,
protocol := Protocol sockport := SockPort,
}, protocol := Protocol
_ConnInfo = #{proto_name := ProtoName, },
proto_ver := ProtoVer, _ConnInfo = #{
connected_at := ConnectedAt proto_name := ProtoName,
}) -> proto_ver := ProtoVer,
#{clientid => ClientId, connected_at := ConnectedAt
username => Username, }
ipaddress => ntoa(PeerHost), ) ->
sockport => SockPort, #{
protocol => Protocol, clientid => ClientId,
proto_name => ProtoName, username => Username,
proto_ver => ProtoVer, ipaddress => ntoa(PeerHost),
connected_at => ConnectedAt, sockport => SockPort,
ts => erlang:system_time(millisecond) protocol => Protocol,
}. proto_name => ProtoName,
proto_ver => ProtoVer,
connected_at => ConnectedAt,
ts => erlang:system_time(millisecond)
}.
ntoa(undefined) -> undefined; ntoa(undefined) -> undefined;
ntoa({IpAddr, Port}) -> ntoa({IpAddr, Port}) -> iolist_to_binary([inet:ntoa(IpAddr), ":", integer_to_list(Port)]);
iolist_to_binary([inet:ntoa(IpAddr), ":", integer_to_list(Port)]); ntoa(IpAddr) -> iolist_to_binary(inet:ntoa(IpAddr)).
ntoa(IpAddr) ->
iolist_to_binary(inet:ntoa(IpAddr)).
event_topic(Event, #{clientid := ClientId, protocol := mqtt}) -> event_topic(Event, #{clientid := ClientId, protocol := mqtt}) ->
iolist_to_binary( iolist_to_binary(
[systop("clients"), "/", ClientId, "/", atom_to_binary(Event)]); [systop("clients"), "/", ClientId, "/", atom_to_binary(Event)]
);
event_topic(Event, #{clientid := ClientId, protocol := GwName}) -> event_topic(Event, #{clientid := ClientId, protocol := GwName}) ->
iolist_to_binary( iolist_to_binary(
[systop("gateway"), "/", atom_to_binary(GwName), [
"/clients/", ClientId, "/", atom_to_binary(Event)]). systop("gateway"),
"/",
atom_to_binary(GwName),
"/clients/",
ClientId,
"/",
atom_to_binary(Event)
]
).

View File

@ -21,25 +21,25 @@
-include("types.hrl"). -include("types.hrl").
-include("logger.hrl"). -include("logger.hrl").
-export([start_link/0]). -export([start_link/0]).
%% compress unused warning %% compress unused warning
-export([procinfo/1]). -export([procinfo/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-define(SYSMON, ?MODULE). -define(SYSMON, ?MODULE).
%% @doc Start the system monitor. %% @doc Start the system monitor.
-spec(start_link() -> startlink_ret()). -spec start_link() -> startlink_ret().
start_link() -> start_link() ->
gen_server:start_link({local, ?SYSMON}, ?MODULE, [], []). gen_server:start_link({local, ?SYSMON}, ?MODULE, [], []).
@ -63,23 +63,23 @@ sysm_opts() ->
sysm_opts(maps:to_list(emqx:get_config([sysmon, vm])), []). sysm_opts(maps:to_list(emqx:get_config([sysmon, vm])), []).
sysm_opts([], Acc) -> sysm_opts([], Acc) ->
Acc; Acc;
sysm_opts([{_, disabled}|Opts], Acc) -> sysm_opts([{_, disabled} | Opts], Acc) ->
sysm_opts(Opts, Acc); sysm_opts(Opts, Acc);
sysm_opts([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) -> sysm_opts([{long_gc, Ms} | Opts], Acc) when is_integer(Ms) ->
sysm_opts(Opts, [{long_gc, Ms}|Acc]); sysm_opts(Opts, [{long_gc, Ms} | Acc]);
sysm_opts([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) -> sysm_opts([{long_schedule, Ms} | Opts], Acc) when is_integer(Ms) ->
sysm_opts(Opts, [{long_schedule, Ms}|Acc]); sysm_opts(Opts, [{long_schedule, Ms} | Acc]);
sysm_opts([{large_heap, Size}|Opts], Acc) when is_integer(Size) -> sysm_opts([{large_heap, Size} | Opts], Acc) when is_integer(Size) ->
sysm_opts(Opts, [{large_heap, Size}|Acc]); sysm_opts(Opts, [{large_heap, Size} | Acc]);
sysm_opts([{busy_port, true}|Opts], Acc) -> sysm_opts([{busy_port, true} | Opts], Acc) ->
sysm_opts(Opts, [busy_port|Acc]); sysm_opts(Opts, [busy_port | Acc]);
sysm_opts([{busy_port, false}|Opts], Acc) -> sysm_opts([{busy_port, false} | Opts], Acc) ->
sysm_opts(Opts, Acc); sysm_opts(Opts, Acc);
sysm_opts([{busy_dist_port, true}|Opts], Acc) -> sysm_opts([{busy_dist_port, true} | Opts], Acc) ->
sysm_opts(Opts, [busy_dist_port|Acc]); sysm_opts(Opts, [busy_dist_port | Acc]);
sysm_opts([{busy_dist_port, false}|Opts], Acc) -> sysm_opts([{busy_dist_port, false} | Opts], Acc) ->
sysm_opts(Opts, Acc); sysm_opts(Opts, Acc);
sysm_opts([_Opt|Opts], Acc) -> sysm_opts([_Opt | Opts], Acc) ->
sysm_opts(Opts, Acc). sysm_opts(Opts, Acc).
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
@ -91,70 +91,91 @@ handle_cast(Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info({monitor, Pid, long_gc, Info}, State) -> handle_info({monitor, Pid, long_gc, Info}, State) ->
suppress({long_gc, Pid}, suppress(
fun() -> {long_gc, Pid},
WarnMsg = io_lib:format("long_gc warning: pid = ~p", [Pid]), fun() ->
?SLOG(warning, #{msg => long_gc, WarnMsg = io_lib:format("long_gc warning: pid = ~p", [Pid]),
info => Info, ?SLOG(warning, #{
porcinfo => procinfo(Pid) msg => long_gc,
}), info => Info,
safe_publish(long_gc, WarnMsg) porcinfo => procinfo(Pid)
end, State); }),
safe_publish(long_gc, WarnMsg)
end,
State
);
handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) -> handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) ->
suppress({long_schedule, Pid}, suppress(
fun() -> {long_schedule, Pid},
WarnMsg = io_lib:format("long_schedule warning: pid = ~p", [Pid]), fun() ->
?SLOG(warning, #{msg => long_schedule, WarnMsg = io_lib:format("long_schedule warning: pid = ~p", [Pid]),
info => Info, ?SLOG(warning, #{
procinfo => procinfo(Pid)}), msg => long_schedule,
safe_publish(long_schedule, WarnMsg) info => Info,
end, State); procinfo => procinfo(Pid)
}),
safe_publish(long_schedule, WarnMsg)
end,
State
);
handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) -> handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) ->
suppress({long_schedule, Port}, suppress(
fun() -> {long_schedule, Port},
WarnMsg = io_lib:format("long_schedule warning: port = ~p", [Port]), fun() ->
?SLOG(warning, #{msg => long_schedule, WarnMsg = io_lib:format("long_schedule warning: port = ~p", [Port]),
info => Info, ?SLOG(warning, #{
portinfo => portinfo(Port)}), msg => long_schedule,
safe_publish(long_schedule, WarnMsg) info => Info,
end, State); portinfo => portinfo(Port)
}),
safe_publish(long_schedule, WarnMsg)
end,
State
);
handle_info({monitor, Pid, large_heap, Info}, State) -> handle_info({monitor, Pid, large_heap, Info}, State) ->
suppress({large_heap, Pid}, suppress(
fun() -> {large_heap, Pid},
WarnMsg = io_lib:format("large_heap warning: pid = ~p", [Pid]), fun() ->
?SLOG(warning, #{msg => large_heap, WarnMsg = io_lib:format("large_heap warning: pid = ~p", [Pid]),
info => Info, ?SLOG(warning, #{
procinfo => procinfo(Pid)}), msg => large_heap,
safe_publish(large_heap, WarnMsg) info => Info,
end, State); procinfo => procinfo(Pid)
}),
safe_publish(large_heap, WarnMsg)
end,
State
);
handle_info({monitor, SusPid, busy_port, Port}, State) -> handle_info({monitor, SusPid, busy_port, Port}, State) ->
suppress({busy_port, Port}, suppress(
fun() -> {busy_port, Port},
WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]), fun() ->
?SLOG(warning, #{msg => busy_port, WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
portinfo => portinfo(Port), ?SLOG(warning, #{
procinfo => procinfo(SusPid) msg => busy_port,
}), portinfo => portinfo(Port),
safe_publish(busy_port, WarnMsg) procinfo => procinfo(SusPid)
end, State); }),
safe_publish(busy_port, WarnMsg)
end,
State
);
handle_info({monitor, SusPid, busy_dist_port, Port}, State) -> handle_info({monitor, SusPid, busy_dist_port, Port}, State) ->
suppress({busy_dist_port, Port}, suppress(
fun() -> {busy_dist_port, Port},
WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]), fun() ->
?SLOG(warning, #{msg => busy_dist_port, WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
portinfo => portinfo(Port), ?SLOG(warning, #{
procinfo => procinfo(SusPid)}), msg => busy_dist_port,
safe_publish(busy_dist_port, WarnMsg) portinfo => portinfo(Port),
end, State); procinfo => procinfo(SusPid)
}),
safe_publish(busy_dist_port, WarnMsg)
end,
State
);
handle_info({timeout, _Ref, reset}, State) -> handle_info({timeout, _Ref, reset}, State) ->
{noreply, State#{events := []}, hibernate}; {noreply, State#{events := []}, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{msg => "unexpected_info", info => Info}), ?SLOG(error, #{msg => "unexpected_info", info => Info}),
{noreply, State}. {noreply, State}.
@ -182,13 +203,13 @@ suppress(Key, SuccFun, State = #{events := Events}) ->
{noreply, State}; {noreply, State};
false -> false ->
_ = SuccFun(), _ = SuccFun(),
{noreply, State#{events := [Key|Events]}} {noreply, State#{events := [Key | Events]}}
end. end.
procinfo(Pid) -> procinfo(Pid) ->
[{pid, Pid} | procinfo_l(emqx_vm:get_process_gc_info(Pid))] ++ [{pid, Pid} | procinfo_l(emqx_vm:get_process_gc_info(Pid))] ++
get_proc_lib_initial_call(Pid) ++ get_proc_lib_initial_call(Pid) ++
procinfo_l(emqx_vm:get_process_info(Pid)). procinfo_l(emqx_vm:get_process_info(Pid)).
procinfo_l(undefined) -> []; procinfo_l(undefined) -> [];
procinfo_l(List) -> List. procinfo_l(List) -> List.

View File

@ -26,11 +26,13 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
Childs = [child_spec(emqx_sys), Childs = [
child_spec(emqx_alarm), child_spec(emqx_sys),
child_spec(emqx_sys_mon), child_spec(emqx_alarm),
child_spec(emqx_os_mon), child_spec(emqx_sys_mon),
child_spec(emqx_vm_mon)], child_spec(emqx_os_mon),
child_spec(emqx_vm_mon)
],
{ok, {{one_for_one, 10, 100}, Childs}}. {ok, {{one_for_one, 10, 100}, Childs}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -41,10 +43,11 @@ child_spec(Mod) ->
child_spec(Mod, []). child_spec(Mod, []).
child_spec(Mod, Args) -> child_spec(Mod, Args) ->
#{id => Mod, #{
start => {Mod, start_link, Args}, id => Mod,
restart => permanent, start => {Mod, start_link, Args},
shutdown => 5000, restart => permanent,
type => worker, shutdown => 5000,
modules => [Mod] type => worker,
}. modules => [Mod]
}.

View File

@ -16,50 +16,54 @@
-module(emqx_tables). -module(emqx_tables).
-export([ new/1 -export([
, new/2 new/1,
]). new/2
]).
-export([ lookup_value/2 -export([
, lookup_value/3 lookup_value/2,
]). lookup_value/3
]).
-export([delete/1]). -export([delete/1]).
%% Create an ets table. %% Create an ets table.
-spec(new(atom()) -> ok). -spec new(atom()) -> ok.
new(Tab) -> new(Tab) ->
new(Tab, []). new(Tab, []).
%% Create a named_table ets. %% Create a named_table ets.
-spec(new(atom(), list()) -> ok). -spec new(atom(), list()) -> ok.
new(Tab, Opts) -> new(Tab, Opts) ->
case ets:info(Tab, name) of case ets:info(Tab, name) of
undefined -> undefined ->
_ = ets:new(Tab, lists:usort([named_table | Opts])), _ = ets:new(Tab, lists:usort([named_table | Opts])),
ok; ok;
Tab -> ok Tab ->
ok
end. end.
%% KV lookup %% KV lookup
-spec(lookup_value(ets:tab(), term()) -> any()). -spec lookup_value(ets:tab(), term()) -> any().
lookup_value(Tab, Key) -> lookup_value(Tab, Key) ->
lookup_value(Tab, Key, undefined). lookup_value(Tab, Key, undefined).
-spec(lookup_value(ets:tab(), term(), any()) -> any()). -spec lookup_value(ets:tab(), term(), any()) -> any().
lookup_value(Tab, Key, Def) -> lookup_value(Tab, Key, Def) ->
try ets:lookup_element(Tab, Key, 2) try
ets:lookup_element(Tab, Key, 2)
catch catch
error:badarg -> Def error:badarg -> Def
end. end.
%% Delete the ets table. %% Delete the ets table.
-spec(delete(ets:tab()) -> ok). -spec delete(ets:tab()) -> ok.
delete(Tab) -> delete(Tab) ->
case ets:info(Tab, name) of case ets:info(Tab, name) of
undefined -> ok; undefined ->
ok;
Tab -> Tab ->
ets:delete(Tab), ets:delete(Tab),
ok ok
end. end.

View File

@ -17,20 +17,22 @@
-module(emqx_tls_lib). -module(emqx_tls_lib).
%% version & cipher suites %% version & cipher suites
-export([ default_versions/0 -export([
, integral_versions/1 default_versions/0,
, default_ciphers/0 integral_versions/1,
, selected_ciphers/1 default_ciphers/0,
, integral_ciphers/2 selected_ciphers/1,
, drop_tls13_for_old_otp/1 integral_ciphers/2,
, all_ciphers/0 drop_tls13_for_old_otp/1,
]). all_ciphers/0
]).
%% SSL files %% SSL files
-export([ ensure_ssl_files/2 -export([
, delete_ssl_files/3 ensure_ssl_files/2,
, file_content_as_options/1 delete_ssl_files/3,
]). file_content_as_options/1
]).
-include("logger.hrl"). -include("logger.hrl").
@ -52,7 +54,7 @@ default_versions() -> available_versions().
%% raise an error exception if non of them are available. %% raise an error exception if non of them are available.
%% The input list can be a string/binary of comma separated versions. %% The input list can be a string/binary of comma separated versions.
-spec integral_versions(undefined | string() | binary() | [ssl:tls_version()]) -> -spec integral_versions(undefined | string() | binary() | [ssl:tls_version()]) ->
[ssl:tls_version()]. [ssl:tls_version()].
integral_versions(undefined) -> integral_versions(undefined) ->
integral_versions(default_versions()); integral_versions(default_versions());
integral_versions([]) -> integral_versions([]) ->
@ -66,10 +68,12 @@ integral_versions(Desired) when is_binary(Desired) ->
integral_versions(Desired) -> integral_versions(Desired) ->
Available = available_versions(), Available = available_versions(),
case lists:filter(fun(V) -> lists:member(V, Available) end, Desired) of case lists:filter(fun(V) -> lists:member(V, Available) end, Desired) of
[] -> erlang:error(#{ reason => no_available_tls_version [] ->
, desired => Desired erlang:error(#{
, available => Available reason => no_available_tls_version,
}); desired => Desired,
available => Available
});
Filtered -> Filtered ->
Filtered Filtered
end. end.
@ -89,7 +93,6 @@ all_ciphers(Versions) ->
%% assert non-empty %% assert non-empty
[_ | _] = dedup(lists:append([ssl:cipher_suites(all, V, openssl) || V <- Versions])). [_ | _] = dedup(lists:append([ssl:cipher_suites(all, V, openssl) || V <- Versions])).
%% @doc All Pre-selected TLS ciphers. %% @doc All Pre-selected TLS ciphers.
default_ciphers() -> default_ciphers() ->
selected_ciphers(available_versions()). selected_ciphers(available_versions()).
@ -97,8 +100,12 @@ default_ciphers() ->
%% @doc Pre-selected TLS ciphers for given versions.. %% @doc Pre-selected TLS ciphers for given versions..
selected_ciphers(Vsns) -> selected_ciphers(Vsns) ->
All = all_ciphers(Vsns), All = all_ciphers(Vsns),
dedup(lists:filter(fun(Cipher) -> lists:member(Cipher, All) end, dedup(
lists:flatmap(fun do_selected_ciphers/1, Vsns))). lists:filter(
fun(Cipher) -> lists:member(Cipher, All) end,
lists:flatmap(fun do_selected_ciphers/1, Vsns)
)
).
do_selected_ciphers('tlsv1.3') -> do_selected_ciphers('tlsv1.3') ->
case lists:member('tlsv1.3', proplists:get_value(available, ssl:versions())) of case lists:member('tlsv1.3', proplists:get_value(available, ssl:versions())) of
@ -106,24 +113,49 @@ do_selected_ciphers('tlsv1.3') ->
false -> [] false -> []
end ++ do_selected_ciphers('tlsv1.2'); end ++ do_selected_ciphers('tlsv1.2');
do_selected_ciphers(_) -> do_selected_ciphers(_) ->
[ "ECDHE-ECDSA-AES256-GCM-SHA384", [
"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDH-ECDSA-AES256-GCM-SHA384", "ECDH-RSA-AES256-GCM-SHA384", "ECDHE-RSA-AES256-GCM-SHA384",
"ECDH-ECDSA-AES256-SHA384", "ECDH-RSA-AES256-SHA384", "DHE-DSS-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384",
"DHE-DSS-AES256-SHA256", "AES256-GCM-SHA384", "AES256-SHA256", "ECDHE-RSA-AES256-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-GCM-SHA256", "ECDH-ECDSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-SHA256", "ECDHE-RSA-AES128-SHA256", "ECDH-ECDSA-AES128-GCM-SHA256", "ECDH-RSA-AES256-GCM-SHA384",
"ECDH-RSA-AES128-GCM-SHA256", "ECDH-ECDSA-AES128-SHA256", "ECDH-RSA-AES128-SHA256", "ECDH-ECDSA-AES256-SHA384",
"DHE-DSS-AES128-GCM-SHA256", "DHE-DSS-AES128-SHA256", "AES128-GCM-SHA256", "AES128-SHA256", "ECDH-RSA-AES256-SHA384",
"ECDHE-ECDSA-AES256-SHA", "ECDHE-RSA-AES256-SHA", "DHE-DSS-AES256-SHA", "DHE-DSS-AES256-GCM-SHA384",
"ECDH-ECDSA-AES256-SHA", "ECDH-RSA-AES256-SHA", "ECDHE-ECDSA-AES128-SHA", "DHE-DSS-AES256-SHA256",
"ECDHE-RSA-AES128-SHA", "DHE-DSS-AES128-SHA", "ECDH-ECDSA-AES128-SHA", "AES256-GCM-SHA384",
"ECDH-RSA-AES128-SHA", "AES256-SHA256",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES128-SHA256",
"ECDHE-RSA-AES128-SHA256",
"ECDH-ECDSA-AES128-GCM-SHA256",
"ECDH-RSA-AES128-GCM-SHA256",
"ECDH-ECDSA-AES128-SHA256",
"ECDH-RSA-AES128-SHA256",
"DHE-DSS-AES128-GCM-SHA256",
"DHE-DSS-AES128-SHA256",
"AES128-GCM-SHA256",
"AES128-SHA256",
"ECDHE-ECDSA-AES256-SHA",
"ECDHE-RSA-AES256-SHA",
"DHE-DSS-AES256-SHA",
"ECDH-ECDSA-AES256-SHA",
"ECDH-RSA-AES256-SHA",
"ECDHE-ECDSA-AES128-SHA",
"ECDHE-RSA-AES128-SHA",
"DHE-DSS-AES128-SHA",
"ECDH-ECDSA-AES128-SHA",
"ECDH-RSA-AES128-SHA",
%% psk %% psk
"RSA-PSK-AES256-GCM-SHA384","RSA-PSK-AES256-CBC-SHA384", "RSA-PSK-AES256-GCM-SHA384",
"RSA-PSK-AES128-GCM-SHA256","RSA-PSK-AES128-CBC-SHA256", "RSA-PSK-AES256-CBC-SHA384",
"RSA-PSK-AES256-CBC-SHA","RSA-PSK-AES128-CBC-SHA" "RSA-PSK-AES128-GCM-SHA256",
"RSA-PSK-AES128-CBC-SHA256",
"RSA-PSK-AES256-CBC-SHA",
"RSA-PSK-AES128-CBC-SHA"
]. ].
%% @doc Ensure version & cipher-suites integrity. %% @doc Ensure version & cipher-suites integrity.
@ -146,7 +178,7 @@ integral_ciphers(Versions, Ciphers) ->
ensure_tls13_cipher(true, Ciphers) -> ensure_tls13_cipher(true, Ciphers) ->
Tls13Ciphers = selected_ciphers(['tlsv1.3']), Tls13Ciphers = selected_ciphers(['tlsv1.3']),
case lists:any(fun(C) -> lists:member(C, Tls13Ciphers) end, Ciphers) of case lists:any(fun(C) -> lists:member(C, Tls13Ciphers) end, Ciphers) of
true -> Ciphers; true -> Ciphers;
false -> Tls13Ciphers ++ Ciphers false -> Tls13Ciphers ++ Ciphers
end; end;
ensure_tls13_cipher(false, Ciphers) -> ensure_tls13_cipher(false, Ciphers) ->
@ -172,7 +204,8 @@ dedup([H | T]) -> [H | dedup([I || I <- T, I =/= H])].
parse_versions(Versions) -> parse_versions(Versions) ->
do_parse_versions(split_by_comma(Versions), []). do_parse_versions(split_by_comma(Versions), []).
do_parse_versions([], Acc) -> lists:reverse(Acc); do_parse_versions([], Acc) ->
lists:reverse(Acc);
do_parse_versions([V | More], Acc) -> do_parse_versions([V | More], Acc) ->
case parse_version(V) of case parse_version(V) of
unknown -> unknown ->
@ -209,21 +242,22 @@ drop_tls13_for_old_otp(SslOpts) ->
%% should return when running on otp 23. %% should return when running on otp 23.
%% But we still have to hard-code them because tlsv1.3 on otp 22 is %% But we still have to hard-code them because tlsv1.3 on otp 22 is
%% not trustworthy. %% not trustworthy.
-define(TLSV13_EXCLUSIVE_CIPHERS, [ "TLS_AES_256_GCM_SHA384" -define(TLSV13_EXCLUSIVE_CIPHERS, [
, "TLS_AES_128_GCM_SHA256" "TLS_AES_256_GCM_SHA384",
, "TLS_CHACHA20_POLY1305_SHA256" "TLS_AES_128_GCM_SHA256",
, "TLS_AES_128_CCM_SHA256" "TLS_CHACHA20_POLY1305_SHA256",
, "TLS_AES_128_CCM_8_SHA256" "TLS_AES_128_CCM_SHA256",
]). "TLS_AES_128_CCM_8_SHA256"
]).
drop_tls13(SslOpts0) -> drop_tls13(SslOpts0) ->
SslOpts1 = case maps:find(versions, SslOpts0) of SslOpts1 =
error -> SslOpts0; case maps:find(versions, SslOpts0) of
{ok, Vsns} -> SslOpts0#{versions => (Vsns -- ['tlsv1.3'])} error -> SslOpts0;
end, {ok, Vsns} -> SslOpts0#{versions => (Vsns -- ['tlsv1.3'])}
end,
case maps:find(ciphers, SslOpts1) of case maps:find(ciphers, SslOpts1) of
error -> SslOpts1; error -> SslOpts1;
{ok, Ciphers} -> {ok, Ciphers} -> SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS}
SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS}
end. end.
%% @doc The input map is a HOCON decoded result of a struct defined as %% @doc The input map is a HOCON decoded result of a struct defined as
@ -233,17 +267,19 @@ drop_tls13(SslOpts0) ->
%% When PEM format key or certificate is given, it tries to to save them in the given %% When PEM format key or certificate is given, it tries to to save them in the given
%% sub-dir in emqx's data_dir, and replace saved file paths for SSL options. %% sub-dir in emqx's data_dir, and replace saved file paths for SSL options.
-spec ensure_ssl_files(file:name_all(), undefined | map()) -> -spec ensure_ssl_files(file:name_all(), undefined | map()) ->
{ok, undefined | map()} | {error, map()}. {ok, undefined | map()} | {error, map()}.
ensure_ssl_files(Dir, Opts) -> ensure_ssl_files(Dir, Opts) ->
ensure_ssl_files(Dir, Opts, _DryRun = false). ensure_ssl_files(Dir, Opts, _DryRun = false).
ensure_ssl_files(_Dir, undefined, _DryRun) -> {ok, undefined}; ensure_ssl_files(_Dir, undefined, _DryRun) ->
{ok, undefined};
ensure_ssl_files(_Dir, #{<<"enable">> := False} = Opts, _DryRun) when ?IS_FALSE(False) -> ensure_ssl_files(_Dir, #{<<"enable">> := False} = Opts, _DryRun) when ?IS_FALSE(False) ->
{ok, Opts}; {ok, Opts};
ensure_ssl_files(Dir, Opts, DryRun) -> ensure_ssl_files(Dir, Opts, DryRun) ->
ensure_ssl_files(Dir, Opts, ?SSL_FILE_OPT_NAMES, DryRun). ensure_ssl_files(Dir, Opts, ?SSL_FILE_OPT_NAMES, DryRun).
ensure_ssl_files(_Dir,Opts, [], _DryRun) -> {ok, Opts}; ensure_ssl_files(_Dir, Opts, [], _DryRun) ->
{ok, Opts};
ensure_ssl_files(Dir, Opts, [Key | Keys], DryRun) -> ensure_ssl_files(Dir, Opts, [Key | Keys], DryRun) ->
case ensure_ssl_file(Dir, Key, Opts, maps:get(Key, Opts, undefined), DryRun) of case ensure_ssl_file(Dir, Key, Opts, maps:get(Key, Opts, undefined), DryRun) of
{ok, NewOpts} -> {ok, NewOpts} ->
@ -258,18 +294,25 @@ delete_ssl_files(Dir, NewOpts0, OldOpts0) ->
DryRun = true, DryRun = true,
{ok, NewOpts} = ensure_ssl_files(Dir, NewOpts0, DryRun), {ok, NewOpts} = ensure_ssl_files(Dir, NewOpts0, DryRun),
{ok, OldOpts} = ensure_ssl_files(Dir, OldOpts0, DryRun), {ok, OldOpts} = ensure_ssl_files(Dir, OldOpts0, DryRun),
Get = fun(_K, undefined) -> undefined; Get = fun
(K, Opts) -> maps:get(K, Opts, undefined) (_K, undefined) -> undefined;
end, (K, Opts) -> maps:get(K, Opts, undefined)
lists:foreach(fun(Key) -> delete_old_file(Get(Key, NewOpts), Get(Key, OldOpts)) end, end,
?SSL_FILE_OPT_NAMES). lists:foreach(
fun(Key) -> delete_old_file(Get(Key, NewOpts), Get(Key, OldOpts)) end,
?SSL_FILE_OPT_NAMES
).
delete_old_file(New, Old) when New =:= Old -> ok; delete_old_file(New, Old) when New =:= Old -> ok;
delete_old_file(_New, _Old = undefined) -> ok; delete_old_file(_New, _Old = undefined) ->
ok;
delete_old_file(_New, Old) -> delete_old_file(_New, Old) ->
case filelib:is_regular(Old) andalso file:delete(Old) of case filelib:is_regular(Old) andalso file:delete(Old) of
ok -> ok; ok ->
false -> ok; %% already deleted ok;
%% already deleted
false ->
ok;
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "failed_to_delete_ssl_file", file_path => Old, reason => Reason}) ?SLOG(error, #{msg => "failed_to_delete_ssl_file", file_path => Old, reason => Reason})
end. end.
@ -293,12 +336,14 @@ do_ensure_ssl_file(Dir, Key, Opts, MaybePem, DryRun) ->
end; end;
false -> false ->
case is_valid_pem_file(MaybePem) of case is_valid_pem_file(MaybePem) of
true -> {ok, Opts}; true ->
{ok, Opts};
{error, enoent} when DryRun -> {ok, Opts}; {error, enoent} when DryRun -> {ok, Opts};
{error, Reason} -> {error, Reason} ->
{error, #{pem_check => invalid_pem, {error, #{
file_read => Reason pem_check => invalid_pem,
}} file_read => Reason
}}
end end
end. end.
@ -312,8 +357,10 @@ is_valid_string(Binary) when is_binary(Binary) ->
%% Check if it is a valid PEM formatted key. %% Check if it is a valid PEM formatted key.
is_pem(MaybePem) -> is_pem(MaybePem) ->
try public_key:pem_decode(MaybePem) =/= [] try
catch _ : _ -> false public_key:pem_decode(MaybePem) =/= []
catch
_:_ -> false
end. end.
%% Write the pem file to the given dir. %% Write the pem file to the given dir.
@ -328,8 +375,7 @@ save_pem_file(Dir, Key, Pem, DryRun) ->
ok -> ok ->
case file:write_file(Path, Pem) of case file:write_file(Path, Pem) of
ok -> {ok, Path}; ok -> {ok, Path};
{error, Reason} -> {error, Reason} -> {error, #{failed_to_write_file => Reason, file_path => Path}}
{error, #{failed_to_write_file => Reason, file_path => Path}}
end; end;
{error, Reason} -> {error, Reason} ->
{error, #{failed_to_create_dir_for => Path, reason => Reason}} {error, #{failed_to_create_dir_for => Path, reason => Reason}}
@ -346,7 +392,7 @@ pem_file_name(Dir, Key, Pem) ->
filename:join([emqx:certs_dir(), Dir, FileName]). filename:join([emqx:certs_dir(), Dir, FileName]).
hex_str(Bin) -> hex_str(Bin) ->
iolist_to_binary([io_lib:format("~2.16.0b",[X]) || <<X:8>> <= Bin ]). iolist_to_binary([io_lib:format("~2.16.0b", [X]) || <<X:8>> <= Bin]).
is_valid_pem_file(Path) -> is_valid_pem_file(Path) ->
case file:read_file(Path) of case file:read_file(Path) of
@ -355,7 +401,8 @@ is_valid_pem_file(Path) ->
end. end.
%% @doc This is to return SSL file content in management APIs. %% @doc This is to return SSL file content in management APIs.
file_content_as_options(undefined) -> undefined; file_content_as_options(undefined) ->
undefined;
file_content_as_options(#{<<"enable">> := False} = SSL) when ?IS_FALSE(False) -> file_content_as_options(#{<<"enable">> := False} = SSL) when ?IS_FALSE(False) ->
{ok, maps:without(?SSL_FILE_OPT_NAMES, SSL)}; {ok, maps:without(?SSL_FILE_OPT_NAMES, SSL)};
file_content_as_options(#{<<"enable">> := True} = SSL) when ?IS_TRUE(True) -> file_content_as_options(#{<<"enable">> := True} = SSL) when ?IS_TRUE(True) ->
@ -365,15 +412,17 @@ file_content_as_options([], SSL) ->
{ok, SSL}; {ok, SSL};
file_content_as_options([Key | Keys], SSL) -> file_content_as_options([Key | Keys], SSL) ->
case maps:get(Key, SSL, undefined) of case maps:get(Key, SSL, undefined) of
undefined -> file_content_as_options(Keys, SSL); undefined ->
file_content_as_options(Keys, SSL);
Path -> Path ->
case file:read_file(Path) of case file:read_file(Path) of
{ok, Bin} -> {ok, Bin} ->
file_content_as_options(Keys, SSL#{Key => Bin}); file_content_as_options(Keys, SSL#{Key => Bin});
{error, Reason} -> {error, Reason} ->
{error, #{file_path => Path, {error, #{
reason => Reason file_path => Path,
}} reason => Reason
}}
end end
end. end.

View File

@ -30,19 +30,25 @@ lookup(psk, PSKIdentity, _UserState) ->
{ok, SharedSecret} when is_binary(SharedSecret) -> {ok, SharedSecret} when is_binary(SharedSecret) ->
{ok, SharedSecret}; {ok, SharedSecret};
normal -> normal ->
?SLOG(info, #{msg => "psk_identity_not_found", ?SLOG(info, #{
psk_identity => PSKIdentity}), msg => "psk_identity_not_found",
psk_identity => PSKIdentity
}),
error; error;
{error, Reason} -> {error, Reason} ->
?SLOG(warning, #{msg => "psk_identity_not_found", ?SLOG(warning, #{
psk_identity => PSKIdentity, msg => "psk_identity_not_found",
reason => Reason}), psk_identity => PSKIdentity,
reason => Reason
}),
error error
catch catch
Class:Reason:Stacktrace -> Class:Reason:Stacktrace ->
?SLOG(error, #{msg => "lookup_psk_failed", ?SLOG(error, #{
class => Class, msg => "lookup_psk_failed",
reason => Reason, class => Class,
stacktrace => Stacktrace}), reason => Reason,
error stacktrace => Stacktrace
}),
error
end. end.

Some files were not shown because too many files have changed in this diff Show More