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

@ -49,7 +49,7 @@ populate(_App) ->
%% @doc Read the configuration of an application. %% @doc Read the configuration of an application.
-spec(read(atom()) -> {ok, list(env())} | {error, term()}). -spec(read(atom()) -> {ok, list(env())} | {error, term()}).
read(App) -> read(App) ->
%% TODO: %% TODO:
%% 1. Read the app.conf from etc folder %% 1. Read the app.conf from etc folder
%% 2. Cuttlefish to read the conf %% 2. Cuttlefish to read the conf
%% 3. Return the terms and schema %% 3. Return the terms and schema

View File

@ -228,7 +228,7 @@ find_plugin(Name) ->
find_plugin(Name, list()). find_plugin(Name, list()).
find_plugin(Name, Plugins) -> find_plugin(Name, Plugins) ->
lists:keyfind(Name, 2, Plugins). lists:keyfind(Name, 2, Plugins).
%% @doc UnLoad a Plugin %% @doc UnLoad a Plugin
-spec(unload(atom()) -> ok | {error, term()}). -spec(unload(atom()) -> ok | {error, term()}).

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} ->
@ -685,12 +690,12 @@ get_property(Name, Props, Default) ->
maps:get(Name, Props, Default). maps:get(Name, Props, Default).
make_will_msg(#mqtt_packet_connect{proto_ver = ProtoVer, make_will_msg(#mqtt_packet_connect{proto_ver = ProtoVer,
will_props = WillProps} = Connect) -> will_props = WillProps} = Connect) ->
emqx_packet:will_msg(if emqx_packet:will_msg(if
ProtoVer =:= ?MQTT_PROTO_V5 -> ProtoVer =:= ?MQTT_PROTO_V5 ->
WillDelayInterval = get_property('Will-Delay-Interval', WillProps, 0), WillDelayInterval = get_property('Will-Delay-Interval', WillProps, 0),
Connect#mqtt_packet_connect{will_props = set_property('Will-Delay-Interval', WillDelayInterval, WillProps)}; Connect#mqtt_packet_connect{will_props = set_property('Will-Delay-Interval', WillDelayInterval, WillProps)};
true -> true ->
Connect Connect
end). end).

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

@ -59,7 +59,7 @@ open_session(SessAttrs = #{clean_start := true, client_id := ClientId, conn_pid
end, end,
emqx_sm_locker:trans(ClientId, CleanStart); emqx_sm_locker:trans(ClientId, CleanStart);
open_session(SessAttrs = #{clean_start := false, open_session(SessAttrs = #{clean_start := false,
client_id := ClientId}) -> client_id := ClientId}) ->
ResumeStart = fun(_) -> ResumeStart = fun(_) ->
case resume_session(ClientId, SessAttrs) of case resume_session(ClientId, SessAttrs) of

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

@ -62,7 +62,7 @@ subscribe_unsubscribe(_) ->
ok = emqx:subscribe(<<"topic/2">>, <<"clientId">>, #{ qos => 2 }), ok = emqx:subscribe(<<"topic/2">>, <<"clientId">>, #{ qos => 2 }),
true = emqx:subscribed(<<"topic">>, <<"clientId">>), true = emqx:subscribed(<<"topic">>, <<"clientId">>),
Topics = emqx:topics(), Topics = emqx:topics(),
lists:foreach(fun(Topic) -> lists:foreach(fun(Topic) ->
?assert(lists:member(Topic, Topics)) ?assert(lists:member(Topic, Topics))
end, Topics), end, Topics),
ok = emqx:unsubscribe(<<"topic">>, <<"clientId">>), ok = emqx:unsubscribe(<<"topic">>, <<"clientId">>),

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

@ -21,7 +21,7 @@
all() -> [t_child_all]. all() -> [t_child_all].
t_child_all(_) -> t_child_all(_) ->
{ok, _Pid} = emqx_mod_sup:start_link(), {ok, _Pid} = emqx_mod_sup:start_link(),
{ok, _Child} = emqx_mod_sup:start_child(emqx_banned, worker), {ok, _Child} = emqx_mod_sup:start_child(emqx_banned, worker),
timer:sleep(10), timer:sleep(10),

View File

@ -114,8 +114,8 @@ t_check_sub(_) ->
[{<<"client/stat">>, Opts}], [{<<"client/stat">>, Opts}],
[{<<"client/stat">>, Opts#{rc => ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}}]), [{<<"client/stat">>, Opts#{rc => ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}}]),
ok = do_check_sub(Caps#{mqtt_wildcard_subscription => false}, ok = do_check_sub(Caps#{mqtt_wildcard_subscription => false},
[{<<"vlient/+/dsofi">>, Opts}], [{<<"vlient/+/dsofi">>, Opts}],
[{<<"vlient/+/dsofi">>, Opts#{rc => ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED}}]), [{<<"vlient/+/dsofi">>, Opts#{rc => ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED}}]),
emqx_zone:stop(). emqx_zone:stop().

View File

@ -63,7 +63,7 @@
all() -> [{group, connect}]. all() -> [{group, connect}].
groups() -> [{connect, [sequence], groups() -> [{connect, [sequence],
[case1_protocol_name, [case1_protocol_name,
case2_protocol_ver%, case2_protocol_ver%,
%TOTO case3_invalid_reserved %TOTO case3_invalid_reserved

View File

@ -94,7 +94,7 @@ t_priority_queues(_) ->
PQueue6 = ?PQ:in(f, 1, PQueue5), PQueue6 = ?PQ:in(f, 1, PQueue5),
{{value, e}, PQueue7} = ?PQ:out(PQueue6), {{value, e}, PQueue7} = ?PQ:out(PQueue6),
{empty, _} = ?PQ:out(0, ?PQ:new()), {empty, _} = ?PQ:out(0, ?PQ:new()),
{empty, Q0} = ?PQ:out_p(Q0), {empty, Q0} = ?PQ:out_p(Q0),

View File

@ -132,7 +132,7 @@ connect_v5(_) ->
raw_recv_parse(Data, ?MQTT_PROTO_V5) raw_recv_parse(Data, ?MQTT_PROTO_V5)
end), end),
% test clean start % test clean start
with_connection(fun(Sock) -> with_connection(fun(Sock) ->
emqx_client_sock:send(Sock, emqx_client_sock:send(Sock,
raw_send_serialize( raw_send_serialize(

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

@ -24,13 +24,13 @@ all() -> [t_open_close_session].
t_open_close_session(_) -> t_open_close_session(_) ->
emqx_ct_broker_helpers:run_setup_steps(), emqx_ct_broker_helpers:run_setup_steps(),
{ok, ClientPid} = emqx_mock_client:start_link(<<"client">>), {ok, ClientPid} = emqx_mock_client:start_link(<<"client">>),
Attrs = #{clean_start => true, Attrs = #{clean_start => true,
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,
will_msg => undefined}, will_msg => undefined},
{ok, SPid} = emqx_sm:open_session(Attrs), {ok, SPid} = emqx_sm:open_session(Attrs),
@ -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().

View File

@ -95,7 +95,7 @@
all() -> all() ->
[load, systeminfo, mem_info, process_list, process_info, process_gc, [load, systeminfo, mem_info, process_list, process_info, process_gc,
get_ets_list, get_ets_info, get_ets_object, get_port_types, get_port_info, get_ets_list, get_ets_info, get_ets_object, get_port_types, get_port_info,
scheduler_usage, get_memory, microsecs, schedulers, get_process_group_leader_info, scheduler_usage, get_memory, microsecs, schedulers, get_process_group_leader_info,
get_process_limit]. get_process_limit].
@ -121,13 +121,13 @@ process_list(_Config) ->
true = lists:member({pid, Pid}, lists:concat(ProcessInfo)). true = lists:member({pid, Pid}, lists:concat(ProcessInfo)).
process_info(_Config) -> process_info(_Config) ->
ProcessInfos = emqx_vm:get_process_info(), ProcessInfos = emqx_vm:get_process_info(),
ProcessInfo = lists:last(ProcessInfos), ProcessInfo = lists:last(ProcessInfos),
Keys = [K || {K, _V}<- ProcessInfo], Keys = [K || {K, _V}<- ProcessInfo],
?PROCESS_INFO = Keys. ?PROCESS_INFO = Keys.
process_gc(_Config) -> process_gc(_Config) ->
ProcessGcs = emqx_vm:get_process_gc(), ProcessGcs = emqx_vm:get_process_gc(),
ProcessGc = lists:last(ProcessGcs), ProcessGc = lists:last(ProcessGcs),
Keys = [K || {K, _V}<- ProcessGc], Keys = [K || {K, _V}<- ProcessGc],
?PROCESS_GC = Keys. ?PROCESS_GC = Keys.
@ -137,7 +137,7 @@ get_ets_list(_Config) ->
Ets = emqx_vm:get_ets_list(), Ets = emqx_vm:get_ets_list(),
true = lists:member(test, Ets). true = lists:member(test, Ets).
get_ets_info(_Config) -> get_ets_info(_Config) ->
ets:new(test, [named_table]), ets:new(test, [named_table]),
[] = emqx_vm:get_ets_info(test1), [] = emqx_vm:get_ets_info(test1),
EtsInfo = emqx_vm:get_ets_info(test), EtsInfo = emqx_vm:get_ets_info(test),