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 ## Value: boolean
mqtt.shared_subscription = true 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 ## Zones
##-------------------------------------------------------------------- ##--------------------------------------------------------------------

View File

@ -595,6 +595,12 @@ end}.
{datatype, {enum, [true, false]}} {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 %% Zones
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -201,7 +201,7 @@ aggre(Routes) ->
lists:foldl( lists:foldl(
fun(#route{topic = To, dest = Node}, Acc) when is_atom(Node) -> fun(#route{topic = To, dest = Node}, Acc) when is_atom(Node) ->
[{To, Node} | Acc]; [{To, Node} | Acc];
(#route{topic = To, dest = {Group, _Node}}, Acc) -> (#route{topic = To, dest = {Group, _Node}}, Acc) ->
lists:usort([{To, Group} | Acc]) lists:usort([{To, Group} | Acc])
end, [], Routes). end, [], Routes).

View File

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

View File

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

View File

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

View File

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

View File

@ -18,9 +18,11 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
all() -> [t_session_all]. all() -> [ignore_loop, t_session_all].
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_ct_broker_helpers:run_setup_steps(), emqx_ct_broker_helpers:run_setup_steps(),
@ -29,6 +31,19 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_ct_broker_helpers:run_teardown_steps(). 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(_) -> t_session_all(_) ->
ClientId = <<"ClientId">>, ClientId = <<"ClientId">>,
{ok, ConnPid} = emqx_mock_client:start_link(ClientId), {ok, ConnPid} = emqx_mock_client:start_link(ClientId),

View File

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