Fix issue#1874 (#1964)

* Fix issue#1874
Prior to this change, if user use one client connect emqx with mqtt
v3.1.1, the client subscribe the topic and publish message to this
topic, it would receive this message itself published, this commit
provide a configure option to let user ignore the message itself published.

This change fix issue 1874.

* Small Fix

* Fix bug

* Better design

* Fix compile warning and improve coverage

* Better design to solve the performance issue

* Fix typo

* Fix typo

* Delete spaces in end of lines.

* Do not use anonymous function

* Better performance
This commit is contained in:
Gilbert 2018-11-19 13:34:03 +08:00 committed by turtleDeng
parent bc1464a33f
commit 16821490ce
19 changed files with 68 additions and 35 deletions

View File

@ -474,6 +474,11 @@ mqtt.wildcard_subscription = true
## Value: boolean
mqtt.shared_subscription = true
## Whether to ignore loop delivery of messages.(for mqtt v3.1.1)
##
## Value: true | false
mqtt.ignore_loop_deliver = false
##--------------------------------------------------------------------
## Zones
##--------------------------------------------------------------------

View File

@ -595,6 +595,12 @@ end}.
{datatype, {enum, [true, false]}}
]}.
%% @doc Whether to ignore loop delivery of messages.(for mqtt v3.1.1)
{mapping, "mqtt.ignore_loop_deliver", "emqx.mqtt_ignore_loop_deliver", [
{default, true},
{datatype, {enum, [true, false]}}
]}.
%%--------------------------------------------------------------------
%% Zones
%%--------------------------------------------------------------------

View File

@ -63,7 +63,8 @@
recv_stats,
send_stats,
connected,
connected_at
connected_at,
ignore_loop
}).
-type(state() :: #pstate{}).
@ -71,6 +72,7 @@
-ifdef(TEST).
-compile(export_all).
-compile(nowarn_export_all).
-endif.
-define(NO_PROPS, undefined).
@ -102,7 +104,8 @@ init(#{peername := Peername, peercert := Peercert, sendfun := SendFun}, Options)
enable_acl = emqx_zone:get_env(Zone, enable_acl),
recv_stats = #{msg => 0, pkt => 0},
send_stats = #{msg => 0, pkt => 0},
connected = false}.
connected = false,
ignore_loop = emqx_config:get_env(mqtt_ignore_loop_deliver, false)}.
init_username(Peercert, Options) ->
case proplists:get_value(peer_cert_as_username, Options) of
@ -385,11 +388,14 @@ process_packet(?PUBCOMP_PACKET(PacketId, ReasonCode), PState = #pstate{session =
{ok = emqx_session:pubcomp(SPid, PacketId, ReasonCode), PState};
process_packet(?SUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters),
PState = #pstate{session = SPid, mountpoint = Mountpoint, proto_ver = ProtoVer, is_bridge = IsBridge}) ->
PState = #pstate{session = SPid, mountpoint = Mountpoint,
proto_ver = ProtoVer, is_bridge = IsBridge,
ignore_loop = IgnoreLoop}) ->
RawTopicFilters1 = if ProtoVer < ?MQTT_PROTO_V5 ->
IfIgnoreLoop = case IgnoreLoop of true -> 1; false -> 0 end,
case IsBridge of
true -> [{RawTopic, SubOpts#{rap => 1}} || {RawTopic, SubOpts} <- RawTopicFilters];
false -> [{RawTopic, SubOpts#{rap => 0}} || {RawTopic, SubOpts} <- RawTopicFilters]
true -> [{RawTopic, SubOpts#{rap => 1, nl => IfIgnoreLoop}} || {RawTopic, SubOpts} <- RawTopicFilters];
false -> [{RawTopic, SubOpts#{rap => 0, nl => IfIgnoreLoop}} || {RawTopic, SubOpts} <- RawTopicFilters]
end;
true ->
RawTopicFilters
@ -626,7 +632,6 @@ try_open_session(PState = #pstate{zone = Zone,
clean_start => CleanStart,
will_msg => WillMsg
},
SessAttrs1 = lists:foldl(fun set_session_attrs/2, SessAttrs, [{max_inflight, PState}, {expiry_interval, PState}, {topic_alias_maximum, PState}]),
case emqx_sm:open_session(SessAttrs1) of
{ok, SPid} ->

View File

@ -152,6 +152,7 @@
will_msg :: emqx:message(),
will_delay_timer :: reference() | undefined
}).
-type(spid() :: pid()).
@ -575,7 +576,8 @@ handle_info({dispatch, Topic, Msgs}, State) when is_list(Msgs) ->
%% Dispatch message
handle_info({dispatch, Topic, Msg = #message{headers = Headers}},
State = #state{subscriptions = SubMap, topic_alias_maximum = TopicAliasMaximum}) when is_record(Msg, message) ->
State = #state{subscriptions = SubMap,
topic_alias_maximum = TopicAliasMaximum}) when is_record(Msg, message) ->
TopicAlias = maps:get('Topic-Alias', Headers, undefined),
if
TopicAlias =:= undefined orelse TopicAlias =< TopicAliasMaximum ->
@ -591,6 +593,7 @@ handle_info({dispatch, Topic, Msg = #message{headers = Headers}},
noreply(State)
end;
%% Do nothing if the client has been disconnected.
handle_info({timeout, Timer, retry_delivery}, State = #state{conn_pid = undefined, retry_timer = Timer}) ->
noreply(State#state{retry_timer = undefined});

View File

@ -269,6 +269,7 @@ websocket_info(Info, State) ->
terminate(SockError, _Req, #state{keepalive = Keepalive,
proto_state = ProtoState,
shutdown = Shutdown}) ->
?LOG(debug, "Terminated for ~p, sockerror: ~p",
[Shutdown, SockError]),
emqx_keepalive:cancel(Keepalive),

View File

@ -150,8 +150,7 @@ receive_messages(Count, Msgs) ->
receive
{publish, Msg} ->
receive_messages(Count-1, [Msg|Msgs]);
Other ->
ct:log("~p~n", [Other]),
_Other ->
receive_messages(Count, Msgs)
after 10 ->
Msgs

View File

@ -18,9 +18,11 @@
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() -> [t_session_all].
all() -> [ignore_loop, t_session_all].
init_per_suite(Config) ->
emqx_ct_broker_helpers:run_setup_steps(),
@ -29,6 +31,19 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
emqx_ct_broker_helpers:run_teardown_steps().
ignore_loop(_Config) ->
application:set_env(emqx, mqtt_ignore_loop_deliver, true),
{ok, Client} = emqx_client:start_link(),
{ok, _} = emqx_client:connect(Client),
TestTopic = <<"Self">>,
{ok, _, [2]} = emqx_client:subscribe(Client, TestTopic, qos2),
ok = emqx_client:publish(Client, TestTopic, <<"testmsg">>, 0),
{ok, _} = emqx_client:publish(Client, TestTopic, <<"testmsg">>, 1),
{ok, _} = emqx_client:publish(Client, TestTopic, <<"testmsg">>, 2),
?assertEqual(0, length(emqx_client_SUITE:receive_messages(3))),
ok = emqx_client:disconnect(Client),
application:set_env(emqx, mqtt_ignore_loop_deliver, false).
t_session_all(_) ->
ClientId = <<"ClientId">>,
{ok, ConnPid} = emqx_mock_client:start_link(ClientId),

View File

@ -28,7 +28,7 @@ t_open_close_session(_) ->
client_id => <<"client">>,
conn_pid => ClientPid,
zone => internal,
username => <<"zhou">>,
username => <<"emqx">>,
expiry_interval => 0,
max_inflight => 0,
topic_alias_maximum => 0,
@ -47,4 +47,3 @@ t_open_close_session(_) ->
ok = emqx_sm:close_session(SPid),
[] = emqx_sm:lookup_session(<<"client">>),
emqx_ct_broker_helpers:run_teardown_steps().