From 24ac3e78a25c9e97c7ee82c032ead696ee2220d2 Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Fri, 28 Dec 2018 15:49:38 +0800 Subject: [PATCH 01/41] Add +L vm args for reducing some memory (#2110) --- etc/vm.args.edge | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/etc/vm.args.edge b/etc/vm.args.edge index 20adc41ab..ccf4969e3 100644 --- a/etc/vm.args.edge +++ b/etc/vm.args.edge @@ -92,4 +92,7 @@ #+sct L0-3c0-3p0N0:L4-7c0-3p1N1 ## Sets the mapping of warning messages for error_logger -#+W w \ No newline at end of file +#+W w + +#Prevents loading information about source filenames and line numbers. ++L From d854120023ec58722339bf0ca2628c80e82979cd Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Fri, 28 Dec 2018 16:37:01 +0800 Subject: [PATCH 02/41] Show plugin name if cannot start it (#2111) --- src/emqx_plugins.erl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/emqx_plugins.erl b/src/emqx_plugins.erl index b0f6456dc..479c36a89 100644 --- a/src/emqx_plugins.erl +++ b/src/emqx_plugins.erl @@ -170,10 +170,13 @@ list() -> plugin(CfgFile) -> AppName = app_name(CfgFile), - {ok, Attrs} = application:get_all_key(AppName), - Ver = proplists:get_value(vsn, Attrs, "0"), - Descr = proplists:get_value(description, Attrs, ""), - #plugin{name = AppName, version = Ver, descr = Descr}. + case application:get_all_key(AppName) of + {ok, Attrs} -> + Ver = proplists:get_value(vsn, Attrs, "0"), + Descr = proplists:get_value(description, Attrs, ""), + #plugin{name = AppName, version = Ver, descr = Descr}; + undefined -> error({plugin_not_found, AppName}) + end. %% @doc Load a Plugin -spec(load(atom()) -> ok | {error, term()}). From 6a1ebe299a897e499451bd7a9d31e8372077455f Mon Sep 17 00:00:00 2001 From: turtleDeng Date: Fri, 28 Dec 2018 19:44:41 +0800 Subject: [PATCH 03/41] Merge emqx32 to emqx30 (#2112) --- Makefile | 3 +- etc/emqx.conf | 75 ++++++++++--- priv/emqx.schema | 38 +++++-- rebar.config | 2 +- src/emqx.app.src | 3 +- src/emqx_bridge.erl | 223 +++++++++++++++++++++++++++++-------- src/emqx_client.erl | 8 +- src/emqx_plugins.erl | 5 +- src/emqx_vm.erl | 1 - test/emqx_bridge_SUITE.erl | 23 ++-- 10 files changed, 290 insertions(+), 91 deletions(-) diff --git a/Makefile b/Makefile index 69312a1c2..41cdd5ab8 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PROJECT = emqx PROJECT_DESCRIPTION = EMQ X Broker -DEPS = jsx gproc gen_rpc ekka esockd cowboy +DEPS = jsx gproc gen_rpc ekka esockd cowboy replayq dep_jsx = hex-emqx 2.9.0 dep_gproc = hex-emqx 0.8.0 @@ -11,6 +11,7 @@ dep_gen_rpc = git-emqx https://github.com/emqx/gen_rpc 2.3.0 dep_esockd = git-emqx https://github.com/emqx/esockd v5.4.3 dep_ekka = git-emqx https://github.com/emqx/ekka v0.5.1 dep_cowboy = hex-emqx 2.4.0 +dep_replayq = git-emqx https://github.com/emqx/replayq v0.1.1 NO_AUTOPATCH = cuttlefish diff --git a/etc/emqx.conf b/etc/emqx.conf index cb713c4d0..a47abddf7 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -142,6 +142,12 @@ cluster.autoclean = 5m ## Value: String ## cluster.k8s.namespace = default +## Kubernates Namespace +## +## Value: String +## cluster.k8s.namespace = default + + ##-------------------------------------------------------------------- ## Node ##-------------------------------------------------------------------- @@ -1598,6 +1604,16 @@ bridge.aws.start_type = manual ## Default: 30 seconds bridge.aws.reconnect_interval = 30s +## Retry interval for bridge QoS1 message delivering. +## +## Value: Duration +bridge.aws.retry_interval = 20s + +## Inflight size. +## +## Value: Integer +bridge.aws.max_inflight = 32 + ## Bridge address: node name for local bridge, host:port for remote. ## ## Value: String @@ -1673,16 +1689,33 @@ bridge.aws.subscription.2.topic = cmd/topic2 ## Value: Number bridge.aws.subscription.2.qos = 1 -## Bridge message queue message type. +## If enabled, queue would be written into disk more quickly. +## However, If disabled, some message would be dropped in +## the situation emqx crashed. ## -## Value: Enum -## Example: memory | disk -bridge.aws.mqueue_type = memory +## Value: on | off +bridge.aws.queue.mem_cache = on -## The pending message queue of a bridge. +## Batch size for buffer queue stored ## -## Value: Number -bridge.aws.max_pending_messages = 10000 +## Value: Integer +## default: 1000 +bridge.aws.queue.batch_size = 1000 + +## Base directory for replayq to store messages on disk +## If this config entry is missing or set to undefined, +## replayq works in a mem-only manner. If the config +## entry was set to `bridge.aws.mqueue_type = memory` +## this config entry would have no effect on mqueue +## +## Value: String +bridge.aws.queue.replayq_dir = {{ platform_data_dir }}/emqx_aws_bridge/ + +## Replayq segment size +## +## Value: Bytesize + +bridge.aws.queue.replayq_seg_bytes = 10MB ## Bribge to remote server via SSL. ## @@ -1735,6 +1768,11 @@ bridge.aws.ssl = off ## Default: 30 seconds ## bridge.azure.reconnect_time = 30s +## Retry interval for bridge QoS1 message delivering. +## +## Value: Duration +## bridge.azure.retry_interval = 20s + ## Bridge address: node name for local bridge, host:port for remote. ## ## Value: String @@ -1810,17 +1848,26 @@ bridge.aws.ssl = off ## Value: Number ## bridge.azure.subscription.2.qos = 1 -## Bridge store message type. +## Batch size for buffer queue stored ## -## Value: Enum -## Example: memory | disk -## bridge.azure.store_type = memory +## Value: Integer +## default: 1000 +## bridge.azure.queue.batch_size = 1000 -## The pending message queue of a bridge. +## Base directory for replayq to store messages on disk +## If this config entry is missing or set to undefined, +## replayq works in a mem-only manner. If the config +## entry was set to `bridge.aws.mqueue_type = memory` +## this config entry would have no effect on mqueue ## -## Value: Number -## bridge.azure.max_pending_messages = 10000 +## Value: String +## Default: {{ platform_data_dir }}/emqx_aws_bridge/ +## bridge.azure.queue.replayq_dir = {{ platform_data_dir }}/emqx_aws_bridge/ +## Replayq segment size +## +## Value: Bytesize +## bridge.azure.queue.replayq_seg_bytes = 10MB ## PEM-encoded CA certificates of the bridge. ## diff --git a/priv/emqx.schema b/priv/emqx.schema index f74b53e7f..134931016 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -1495,8 +1495,20 @@ end}. %%-------------------------------------------------------------------- %% Bridges %%-------------------------------------------------------------------- -{mapping, "bridge.$name.mqueue_type", "emqx.bridges", [ - {datatype, {enum, [memory, disk]}} +{mapping, "bridge.$name.queue.mem_cache", "emqx.bridges", [ + {datatype, flag} +]}. + +{mapping, "bridge.$name.queue.batch_size", "emqx.bridges", [ + {datatype, integer} +]}. + +{mapping, "bridge.$name.queue.replayq_dir", "emqx.bridges", [ + {datatype, string} +]}. + +{mapping, "bridge.$name.queue.replayq_seg_bytes", "emqx.bridges", [ + {datatype, bytesize} ]}. {mapping, "bridge.$name.address", "emqx.bridges", [ @@ -1554,11 +1566,6 @@ end}. {datatype, string} ]}. -{mapping, "bridge.$name.max_pending_messages", "emqx.bridges", [ - {default, 10000}, - {datatype, integer} -]}. - {mapping, "bridge.$name.keepalive", "emqx.bridges", [ {default, "10s"}, {datatype, {duration, ms}} @@ -1587,6 +1594,15 @@ end}. {datatype, {duration, ms}} ]}. +{mapping, "bridge.$name.retry_interval", "emqx.bridges", [ + {default, "20s"}, + {datatype, {duration, ms}} +]}. + +{mapping, "bridge.$name.max_inflight", "emqx.bridges", [ + {default, 0}, + {datatype, integer} +]}. {translation, "emqx.bridges", fun(Conf) -> Split = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end, @@ -1618,7 +1634,11 @@ end}. [{Opt, Val}|Opts] end end, - + Queue = fun(Name) -> + Configs = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".queue", Conf), + QOpts = [{list_to_atom(QOpt), QValue}|| {[_, _, "queue", QOpt], QValue} <- Configs], + maps:from_list(QOpts) + end, Subscriptions = fun(Name) -> Configs = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".subscription", Conf), lists:zip([Topic || {_, Topic} <- lists:sort([{I, Topic} || {[_, _, "subscription", I, "topic"], Topic} <- Configs])], @@ -1629,7 +1649,7 @@ end}. lists:foldl( fun({["bridge", Name, Opt], Val}, Acc) -> %% e.g #{aws => [{OptKey, OptVal}]} - Init = [{list_to_atom(Opt), Val},{subscriptions, Subscriptions(Name)}], + Init = [{list_to_atom(Opt), Val},{subscriptions, Subscriptions(Name)}, {queue, Queue(Name)}], maps:update_with(list_to_atom(Name), fun(Opts) -> Merge(list_to_atom(Opt), Val, Opts) end, Init, Acc); (_, Acc) -> Acc diff --git a/rebar.config b/rebar.config index d3dabae81..8fa9663ae 100644 --- a/rebar.config +++ b/rebar.config @@ -7,6 +7,7 @@ {github_emqx_deps, [{gen_rpc, "2.3.0"}, {ekka, "v0.5.1"}, + {replayq, "v0.1.1"}, {esockd, "v5.4.3"}, {cuttlefish, "v2.2.0"} ]}. @@ -26,4 +27,3 @@ {cover_export_enabled, true}. {plugins, [coveralls]}. - diff --git a/src/emqx.app.src b/src/emqx.app.src index ce643634e..f23bf4792 100644 --- a/src/emqx.app.src +++ b/src/emqx.app.src @@ -3,8 +3,7 @@ {vsn,"git"}, {modules,[]}, {registered,[emqx_sup]}, - {applications,[kernel,stdlib,jsx,gproc,gen_rpc,esockd, - cowboy]}, + {applications,[kernel,stdlib,jsx,gproc,gen_rpc,esockd,cowboy]}, {env,[]}, {mod,{emqx_app,[]}}, {maintainers,["Feng Lee "]}, diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index b1ccdc44b..46107e877 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -1,3 +1,4 @@ + %% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,9 +31,17 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {client_pid, options, reconnect_interval, - mountpoint, queue, mqueue_type, max_pending_messages, - forwards = [], subscriptions = []}). +-record(state, {client_pid :: pid(), + options :: list(), + reconnect_interval :: pos_integer(), + mountpoint :: binary(), + readq :: list(), + writeq :: list(), + replayq :: map(), + ackref :: replayq:ack_ref(), + queue_option :: map(), + forwards :: list(), + subscriptions :: list()}). -record(mqtt_msg, {qos = ?QOS_0, retain = false, dup = false, packet_id, topic, props, payload}). @@ -104,20 +113,85 @@ init([Options]) -> auto -> erlang:send_after(1000, self(), start) end, ReconnectInterval = get_value(reconnect_interval, Options, 30000), - MaxPendingMsg = get_value(max_pending_messages, Options, 10000), Mountpoint = format_mountpoint(get_value(mountpoint, Options)), - MqueueType = get_value(mqueue_type, Options, memory), - Queue = [], + QueueOptions = get_value(queue, Options), {ok, #state{mountpoint = Mountpoint, - queue = Queue, - mqueue_type = MqueueType, + queue_option = QueueOptions, + readq = [], + writeq = [], options = Options, - reconnect_interval = ReconnectInterval, - max_pending_messages = MaxPendingMsg}}. + reconnect_interval = ReconnectInterval}}. -handle_call(start_bridge, _From, State = #state{client_pid = undefined}) -> - {noreply, NewState} = handle_info(start, State), - {reply, #{msg => <<"start bridge successfully">>}, NewState}; +handle_call(start_bridge, _From, State = #state{options = Options, + replayq = undefined, + client_pid = undefined, + queue_option = #{batch_size := BatchSize, + replayq_dir := ReplayqDir, + replayq_seg_bytes := ReplayqSegBytes}}) -> + case emqx_client:start_link([{owner, self()}|options(Options)]) of + {ok, ClientPid} -> + case emqx_client:connect(ClientPid) of + {ok, _} -> + emqx_logger:info("[Bridge] connected to remote successfully"), + Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), + Forwards = subscribe_local_topics(Options), + ReplayQ = replayq:open(#{dir => ReplayqDir, + seg_bytes => ReplayqSegBytes, + sizer => fun(Term) -> + size(term_to_binary(Term)) + end, + marshaller => fun({PktId, Msg}) -> + term_to_binary({PktId, Msg}); + (Bin) -> + binary_to_term(Bin) + end + }), + {NewReplayQ, AckRef, ReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), + {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), + {reply, #{msg => <<"start bridge successfully">>}, State#state{client_pid = ClientPid, + subscriptions = Subs, + readq = NewReadQ, + replayq = NewReplayQ, + ackref = AckRef, + forwards = Forwards}}; + {error, Reason} -> + emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), + {reply, #{msg => <<"connect to remote failed">>}, State#state{client_pid = ClientPid}} + end; + {error, Reason} -> + emqx_logger:error("[Bridge] start failed! error: ~p", [Reason]), + {reply, #{msg => <<"start bridge failed">>}, State} + end; + + +handle_call(start_bridge, _From, State = #state{options = Options, + client_pid = undefined, + replayq = ReplayQ, + queue_option = #{batch_size := BatchSize} + }) -> + case emqx_client:start_link([{owner, self()} | options(Options)]) of + {ok, ClientPid} -> + case emqx_client:connect(ClientPid) of + {ok, _} -> + emqx_logger:info("[Bridge] connected to remote ysucessfully"), + Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), + Forwards = subscribe_local_topics(Options), + {NewReplayQ, AckRef, ReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), + {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), + {reply, #{msg => <<"start bridge successfully">>}, State#state{client_pid = ClientPid, + subscriptions = Subs, + readq = NewReadQ, + replayq = NewReplayQ, + ackref = AckRef, + forwards = Forwards}}; + {error, Reason} -> + emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), + {reply, #{msg => <<"connect to remote failed">>}, State#state{client_pid = ClientPid}} + end; + {error, Reason} -> + emqx_logger:error("[Bridge] restart failed! error: ~p", [Reason]), + {reply, #{msg => <<"start bridge failed">>}, State} + end; handle_call(start_bridge, _From, State) -> {reply, #{msg => <<"bridge already started">>}, State}; @@ -184,46 +258,82 @@ handle_cast(Msg, State) -> emqx_logger:error("[Bridge] unexpected cast: ~p", [Msg]), {noreply, State}. -%%---------------------------------------------------------------- -%% start message bridge -%%---------------------------------------------------------------- -handle_info(start, State = #state{options = Options, - client_pid = undefined}) -> - case emqx_client:start_link([{owner, self()}|options(Options)]) of +handle_info(restart, State = #state{options = Options, + client_pid = undefined, + replayq = ReplayQ, + queue_option = #{batch_size := BatchSize} + }) -> + case emqx_client:start_link([{owner, self()} | options(Options)]) of {ok, ClientPid} -> case emqx_client:connect(ClientPid) of {ok, _} -> - emqx_logger:info("[Bridge] connected to remote sucessfully"), + emqx_logger:info("[Bridge] connected to remote successfully"), Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), - Forwards = subscribe_local_topics(get_value(forwards, Options, [])), + Forwards = subscribe_local_topics(Options), + {NewReplayQ, AckRef, ReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), + {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), {noreply, State#state{client_pid = ClientPid, subscriptions = Subs, + readq = NewReadQ, + replayq = NewReplayQ, + ackref = AckRef, forwards = Forwards}}; {error, Reason} -> emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), {noreply, State#state{client_pid = ClientPid}} end; {error, Reason} -> - emqx_logger:error("[Bridge] start failed! error: ~p", [Reason]), + emqx_logger:error("[Bridge] restart failed! error: ~p", [Reason]), {noreply, State} end; +%%---------------------------------------------------------------- +%% pop message from replayq and publish again +%%---------------------------------------------------------------- +handle_info(pop, State = #state{writeq = WriteQ, replayq = ReplayQ, + queue_option = #{batch_size := BatchSize}}) -> + {NewReplayQ, AckRef, NewReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), + {NewReadQ1, NewWriteQ} = case NewReadQ of + [] -> {WriteQ, []}; + _ -> {NewReadQ, WriteQ} + end, + self() ! replay, + {noreply, State#state{readq = NewReadQ1, writeq = NewWriteQ, replayq = NewReplayQ, ackref = AckRef}}; + +handle_info(dump, State = #state{writeq = WriteQ, replayq = ReplayQ}) -> + NewReplayQueue = replayq:append(ReplayQ, lists:reverse(WriteQ)), + {noreply, State#state{replayq = NewReplayQueue, writeq = []}}; + +%%---------------------------------------------------------------- +%% replay message from replayq +%%---------------------------------------------------------------- +handle_info(replay, State = #state{client_pid = ClientPid, readq = ReadQ}) -> + {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), + {noreply, State#state{readq = NewReadQ}}; + %%---------------------------------------------------------------- %% received local node message %%---------------------------------------------------------------- handle_info({dispatch, _, #message{topic = Topic, payload = Payload, flags = #{retain := Retain}}}, - State = #state{client_pid = Pid, mountpoint = Mountpoint, queue = Queue, - mqueue_type = MqueueType, max_pending_messages = MaxPendingMsg}) -> + State = #state{client_pid = undefined, + mountpoint = Mountpoint}) -> + Msg = #mqtt_msg{qos = 1, + retain = Retain, + topic = mountpoint(Mountpoint, Topic), + payload = Payload}, + {noreply, en_writeq({undefined, Msg}, State)}; +handle_info({dispatch, _, #message{topic = Topic, payload = Payload, flags = #{retain := Retain}}}, + State = #state{client_pid = Pid, mountpoint = Mountpoint}) -> Msg = #mqtt_msg{qos = 1, retain = Retain, topic = mountpoint(Mountpoint, Topic), payload = Payload}, case emqx_client:publish(Pid, Msg) of - {ok, PkgId} -> - {noreply, State#state{queue = store(MqueueType, {PkgId, Msg}, Queue, MaxPendingMsg)}}; - {error, Reason} -> + {ok, PktId} -> + {noreply, en_writeq({PktId, Msg}, State)}; + {error, {PktId, Reason}} -> emqx_logger:error("[Bridge] Publish fail:~p", [Reason]), - {noreply, State} + {noreply, en_writeq({PktId, Msg}, State)} end; %%---------------------------------------------------------------- @@ -239,18 +349,19 @@ handle_info({publish, #{qos := QoS, dup := Dup, retain := Retain, topic := Topic %%---------------------------------------------------------------- %% received remote puback message %%---------------------------------------------------------------- -handle_info({puback, #{packet_id := PkgId}}, State = #state{queue = Queue, mqueue_type = MqueueType}) -> - % lists:keydelete(PkgId, 1, Queue) - {noreply, State#state{queue = delete(MqueueType, PkgId, Queue)}}; +handle_info({puback, #{packet_id := PktId}}, State) -> + {noreply, delete(PktId, State)}; handle_info({'EXIT', Pid, normal}, State = #state{client_pid = Pid}) -> emqx_logger:warning("[Bridge] stop ~p", [normal]), + self() ! dump, {noreply, State#state{client_pid = undefined}}; handle_info({'EXIT', Pid, Reason}, State = #state{client_pid = Pid, reconnect_interval = ReconnectInterval}) -> emqx_logger:error("[Bridge] stop ~p", [Reason]), - erlang:send_after(ReconnectInterval, self(), start), + self() ! dump, + erlang:send_after(ReconnectInterval, self(), restart), {noreply, State#state{client_pid = undefined}}; handle_info(Info, State) -> @@ -267,8 +378,10 @@ subscribe_remote_topics(ClientPid, Subscriptions) -> [begin emqx_client:subscribe(ClientPid, {bin(Topic), Qos}), {bin(Topic), Qos} end || {Topic, Qos} <- Subscriptions, emqx_topic:validate({filter, bin(Topic)})]. -subscribe_local_topics(Topics) -> - [begin emqx_broker:subscribe(bin(Topic)), bin(Topic) end +subscribe_local_topics(Options) -> + Topics = get_value(forwards, Options, []), + Subid = get_value(client_id, Options, <<"bridge">>), + [begin emqx_broker:subscribe(bin(Topic), #{qos => 1, subid => Subid}), bin(Topic) end || Topic <- Topics, emqx_topic:validate({filter, bin(Topic)})]. proto_ver(mqttv3) -> v3; @@ -320,15 +433,35 @@ format_mountpoint(undefined) -> format_mountpoint(Prefix) -> binary:replace(bin(Prefix), <<"${node}">>, atom_to_binary(node(), utf8)). -store(memory, Data, Queue, MaxPendingMsg) when length(Queue) =< MaxPendingMsg -> - [Data | Queue]; -store(memory, _Data, Queue, _MaxPendingMsg) -> - logger:error("Beyond max pending messages"), - Queue; -store(disk, Data, Queue, _MaxPendingMsg)-> - [Data | Queue]. -delete(memory, PkgId, Queue) -> - lists:keydelete(PkgId, 1, Queue); -delete(disk, PkgId, Queue) -> - lists:keydelete(PkgId, 1, Queue). +en_writeq(Msg, State = #state{replayq = ReplayQ, + queue_option = #{mem_cache := false}}) -> + NewReplayQ = replayq:append(ReplayQ, [Msg]), + State#state{replayq = NewReplayQ}; +en_writeq(Msg, State = #state{writeq = WriteQ, + queue_option = #{batch_size := BatchSize, + mem_cache := true}}) + when length(WriteQ) < BatchSize-> + State#state{writeq = [Msg | WriteQ]} ; +en_writeq(Msg, State = #state{writeq = WriteQ, replayq = ReplayQ, + queue_option = #{mem_cache := true}}) -> + NewReplayQ =replayq:append(ReplayQ, lists:reverse(WriteQ)), + State#state{writeq = [Msg], replayq = NewReplayQ}. + +publish_readq_msg(_ClientPid, [], ReadQ) -> + {ok, ReadQ}; +publish_readq_msg(ClientPid, [{_PktId, Msg} | ReadQ], ReadQ) -> + io:format("~n replay msg: ~p ~n", [Msg]), + {ok, PktId} = emqx_client:publish(ClientPid, Msg), + publish_readq_msg(ClientPid, ReadQ, [{PktId, Msg} | ReadQ]). + +delete(_PktId, State = #state{readq = [], writeq = [], replayq = ReplayQ, ackref = AckRef}) -> + ok = replayq:ack(ReplayQ, AckRef), + self() ! pop, + State; + +delete(PktId, State = #state{readq = [], writeq = WriteQ}) -> + State#state{writeq = lists:keydelete(PktId, 1, WriteQ)}; + +delete(PktId, State = #state{readq = ReadQ}) -> + State#state{readq = lists:keydelete(PktId, 1, ReadQ)}. diff --git a/src/emqx_client.erl b/src/emqx_client.erl index 2d9cb0a80..6fbb29724 100644 --- a/src/emqx_client.erl +++ b/src/emqx_client.erl @@ -199,7 +199,7 @@ start_link() -> start_link([]). start_link(Options) when is_map(Options) -> start_link(maps:to_list(Options)); start_link(Options) when is_list(Options) -> - ok = emqx_mqtt_props:validate( + ok = emqx_mqtt_props:validate( proplists:get_value(properties, Options, #{})), case proplists:get_value(name, Options) of undefined -> @@ -769,7 +769,7 @@ connected({call, From}, {publish, Msg = #mqtt_msg{qos = QoS}}, when (QoS =:= ?QOS_1); (QoS =:= ?QOS_2) -> case emqx_inflight:is_full(Inflight) of true -> - {keep_state, State, [{reply, From, {error, inflight_full}}]}; + {keep_state, State, [{reply, From, {error, {PacketId, inflight_full}}}]}; false -> Msg1 = Msg#mqtt_msg{packet_id = PacketId}, case send(Msg1, State) of @@ -777,8 +777,8 @@ connected({call, From}, {publish, Msg = #mqtt_msg{qos = QoS}}, Inflight1 = emqx_inflight:insert(PacketId, {publish, Msg1, os:timestamp()}, Inflight), {keep_state, ensure_retry_timer(NewState#state{inflight = Inflight1}), [{reply, From, {ok, PacketId}}]}; - Error = {error, Reason} -> - {stop_and_reply, Reason, [{reply, From, Error}]} + {error, Reason} -> + {stop_and_reply, Reason, [{reply, From, {error, {PacketId, Reason}}}]} end end; diff --git a/src/emqx_plugins.erl b/src/emqx_plugins.erl index 479c36a89..ea3190a35 100644 --- a/src/emqx_plugins.erl +++ b/src/emqx_plugins.erl @@ -146,7 +146,7 @@ unload() -> with_loaded_file(File, fun stop_plugins/1) end. -%% stop plugins +%% Stop plugins stop_plugins(Names) -> [stop_app(App) || App <- Names]. @@ -252,7 +252,7 @@ unload_plugin(App, Persistent) -> {error, Reason} -> {error, Reason} end. - + stop_app(App) -> case application:stop(App) of ok -> @@ -329,4 +329,3 @@ write_loaded(AppNames) -> emqx_logger:error("Open File ~p Error: ~p", [File, Error]), {error, Error} end. - diff --git a/src/emqx_vm.erl b/src/emqx_vm.erl index 74b815795..15ae00689 100644 --- a/src/emqx_vm.erl +++ b/src/emqx_vm.erl @@ -418,4 +418,3 @@ mapping([{owner, V}|Entries], Acc) when is_pid(V) -> mapping(Entries, [{owner, Owner}|Acc]); mapping([{Key, Value}|Entries], Acc) -> mapping(Entries, [{Key, Value}|Acc]). - diff --git a/test/emqx_bridge_SUITE.erl b/test/emqx_bridge_SUITE.erl index f337e3b4e..bd2894101 100644 --- a/test/emqx_bridge_SUITE.erl +++ b/test/emqx_bridge_SUITE.erl @@ -31,27 +31,28 @@ end_per_suite(_Config) -> emqx_ct_broker_helpers:run_teardown_steps(). bridge_test(_) -> - {ok, _Pid} = emqx_bridge:start_link(emqx, []), #{msg := <<"start bridge successfully">>} - = emqx_bridge:start_bridge(emqx), + = emqx_bridge:start_bridge(aws), test_forwards(), test_subscriptions(0), test_subscriptions(1), test_subscriptions(2), #{msg := <<"stop bridge successfully">>} - = emqx_bridge:stop_bridge(emqx), + = emqx_bridge:stop_bridge(aws), ok. test_forwards() -> - emqx_bridge:add_forward(emqx, <<"test_forwards">>), - [<<"test_forwards">>] = emqx_bridge:show_forwards(emqx), - emqx_bridge:del_forward(emqx, <<"test_forwards">>), - [] = emqx_bridge:show_forwards(emqx), + emqx_bridge:add_forward(aws, <<"test_forwards">>), + [<<"test_forwards">>, <<"topic1/#">>, <<"topic2/#">>] = emqx_bridge:show_forwards(aws), + emqx_bridge:del_forward(aws, <<"test_forwards">>), + [<<"topic1/#">>, <<"topic2/#">>] = emqx_bridge:show_forwards(aws), ok. test_subscriptions(QoS) -> - emqx_bridge:add_subscription(emqx, <<"test_subscriptions">>, QoS), - [{<<"test_subscriptions">>, QoS}] = emqx_bridge:show_subscriptions(emqx), - emqx_bridge:del_subscription(emqx, <<"test_subscriptions">>), - [] = emqx_bridge:show_subscriptions(emqx), + emqx_bridge:add_subscription(aws, <<"test_subscriptions">>, QoS), + [{<<"test_subscriptions">>, QoS}, + {<<"cmd/topic1">>, 1}, + {<<"cmd/topic2">>, 1}] = emqx_bridge:show_subscriptions(aws), + emqx_bridge:del_subscription(aws, <<"test_subscriptions">>), + [{<<"cmd/topic1">>,1}, {<<"cmd/topic2">>,1}] = emqx_bridge:show_subscriptions(aws), ok. From eed03ee2979db1fcc6f721b661d4e46687ac1593 Mon Sep 17 00:00:00 2001 From: Gilbert Wong Date: Fri, 28 Dec 2018 19:51:19 +0800 Subject: [PATCH 04/41] Add replayq in emqx.app.src --- src/emqx.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emqx.app.src b/src/emqx.app.src index f23bf4792..d4613a5af 100644 --- a/src/emqx.app.src +++ b/src/emqx.app.src @@ -3,7 +3,7 @@ {vsn,"git"}, {modules,[]}, {registered,[emqx_sup]}, - {applications,[kernel,stdlib,jsx,gproc,gen_rpc,esockd,cowboy]}, + {applications,[kernel,stdlib,jsx,gproc,gen_rpc,esockd,cowboy,replayq]}, {env,[]}, {mod,{emqx_app,[]}}, {maintainers,["Feng Lee "]}, From 72791b569e3302e5473ffa978e7dcb09cfe8d885 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Fri, 28 Dec 2018 20:04:52 +0800 Subject: [PATCH 05/41] Improve app src (#2114) * Add replayq in emqx.app.src * Fix publish_readq_msg badmatch --- src/emqx_bridge.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index 46107e877..55bf162eb 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -448,12 +448,11 @@ en_writeq(Msg, State = #state{writeq = WriteQ, replayq = ReplayQ, NewReplayQ =replayq:append(ReplayQ, lists:reverse(WriteQ)), State#state{writeq = [Msg], replayq = NewReplayQ}. -publish_readq_msg(_ClientPid, [], ReadQ) -> - {ok, ReadQ}; -publish_readq_msg(ClientPid, [{_PktId, Msg} | ReadQ], ReadQ) -> - io:format("~n replay msg: ~p ~n", [Msg]), +publish_readq_msg(_ClientPid, [], NewReadQ) -> + {ok, NewReadQ}; +publish_readq_msg(ClientPid, [{_PktId, Msg} | ReadQ], NewReadQ) -> {ok, PktId} = emqx_client:publish(ClientPid, Msg), - publish_readq_msg(ClientPid, ReadQ, [{PktId, Msg} | ReadQ]). + publish_readq_msg(ClientPid, ReadQ, [{PktId, Msg} | NewReadQ]). delete(_PktId, State = #state{readq = [], writeq = [], replayq = ReplayQ, ackref = AckRef}) -> ok = replayq:ack(ReplayQ, AckRef), From 9a2573d54b01577b23ad0fc9607978a4d1de0d5f Mon Sep 17 00:00:00 2001 From: Gilbert Date: Thu, 3 Jan 2019 09:10:43 +0800 Subject: [PATCH 06/41] Refactor bridge (#2117) * Refactor bridge --- src/emqx_bridge.erl | 160 +++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 100 deletions(-) diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index 55bf162eb..df2c3ff07 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -1,4 +1,3 @@ - %% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); @@ -122,76 +121,9 @@ init([Options]) -> options = Options, reconnect_interval = ReconnectInterval}}. -handle_call(start_bridge, _From, State = #state{options = Options, - replayq = undefined, - client_pid = undefined, - queue_option = #{batch_size := BatchSize, - replayq_dir := ReplayqDir, - replayq_seg_bytes := ReplayqSegBytes}}) -> - case emqx_client:start_link([{owner, self()}|options(Options)]) of - {ok, ClientPid} -> - case emqx_client:connect(ClientPid) of - {ok, _} -> - emqx_logger:info("[Bridge] connected to remote successfully"), - Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), - Forwards = subscribe_local_topics(Options), - ReplayQ = replayq:open(#{dir => ReplayqDir, - seg_bytes => ReplayqSegBytes, - sizer => fun(Term) -> - size(term_to_binary(Term)) - end, - marshaller => fun({PktId, Msg}) -> - term_to_binary({PktId, Msg}); - (Bin) -> - binary_to_term(Bin) - end - }), - {NewReplayQ, AckRef, ReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), - {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), - {reply, #{msg => <<"start bridge successfully">>}, State#state{client_pid = ClientPid, - subscriptions = Subs, - readq = NewReadQ, - replayq = NewReplayQ, - ackref = AckRef, - forwards = Forwards}}; - {error, Reason} -> - emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), - {reply, #{msg => <<"connect to remote failed">>}, State#state{client_pid = ClientPid}} - end; - {error, Reason} -> - emqx_logger:error("[Bridge] start failed! error: ~p", [Reason]), - {reply, #{msg => <<"start bridge failed">>}, State} - end; - - -handle_call(start_bridge, _From, State = #state{options = Options, - client_pid = undefined, - replayq = ReplayQ, - queue_option = #{batch_size := BatchSize} - }) -> - case emqx_client:start_link([{owner, self()} | options(Options)]) of - {ok, ClientPid} -> - case emqx_client:connect(ClientPid) of - {ok, _} -> - emqx_logger:info("[Bridge] connected to remote ysucessfully"), - Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), - Forwards = subscribe_local_topics(Options), - {NewReplayQ, AckRef, ReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), - {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), - {reply, #{msg => <<"start bridge successfully">>}, State#state{client_pid = ClientPid, - subscriptions = Subs, - readq = NewReadQ, - replayq = NewReplayQ, - ackref = AckRef, - forwards = Forwards}}; - {error, Reason} -> - emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), - {reply, #{msg => <<"connect to remote failed">>}, State#state{client_pid = ClientPid}} - end; - {error, Reason} -> - emqx_logger:error("[Bridge] restart failed! error: ~p", [Reason]), - {reply, #{msg => <<"start bridge failed">>}, State} - end; +handle_call(start_bridge, _From, State = #state{client_pid = undefined}) -> + {Msg, NewState} = bridge(start, State), + {reply, #{msg => Msg}, NewState}; handle_call(start_bridge, _From, State) -> {reply, #{msg => <<"bridge already started">>}, State}; @@ -258,34 +190,16 @@ handle_cast(Msg, State) -> emqx_logger:error("[Bridge] unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info(restart, State = #state{options = Options, - client_pid = undefined, - replayq = ReplayQ, - queue_option = #{batch_size := BatchSize} - }) -> - case emqx_client:start_link([{owner, self()} | options(Options)]) of - {ok, ClientPid} -> - case emqx_client:connect(ClientPid) of - {ok, _} -> - emqx_logger:info("[Bridge] connected to remote successfully"), - Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), - Forwards = subscribe_local_topics(Options), - {NewReplayQ, AckRef, ReadQ} = replayq:pop(ReplayQ, #{count_limit => BatchSize}), - {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), - {noreply, State#state{client_pid = ClientPid, - subscriptions = Subs, - readq = NewReadQ, - replayq = NewReplayQ, - ackref = AckRef, - forwards = Forwards}}; - {error, Reason} -> - emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), - {noreply, State#state{client_pid = ClientPid}} - end; - {error, Reason} -> - emqx_logger:error("[Bridge] restart failed! error: ~p", [Reason]), - {noreply, State} - end; +%%---------------------------------------------------------------- +%% Start or restart bridge +%%---------------------------------------------------------------- +handle_info(start, State) -> + {_Msg, NewState} = bridge(start, State), + {noreply, NewState}; + +handle_info(restart, State) -> + {_Msg, NewState} = bridge(restart, State), + {noreply, NewState}; %%---------------------------------------------------------------- %% pop message from replayq and publish again @@ -296,7 +210,7 @@ handle_info(pop, State = #state{writeq = WriteQ, replayq = ReplayQ, {NewReadQ1, NewWriteQ} = case NewReadQ of [] -> {WriteQ, []}; _ -> {NewReadQ, WriteQ} - end, + end, self() ! replay, {noreply, State#state{readq = NewReadQ1, writeq = NewWriteQ, replayq = NewReplayQ, ackref = AckRef}}; @@ -464,3 +378,49 @@ delete(PktId, State = #state{readq = [], writeq = WriteQ}) -> delete(PktId, State = #state{readq = ReadQ}) -> State#state{readq = lists:keydelete(PktId, 1, ReadQ)}. + +bridge(Action, State = #state{options = Options, + replayq = ReplayQ, + queue_option = QueueOption}) -> + case emqx_client:start_link([{owner, self()}|options(Options)]) of + {ok, ClientPid} -> + case emqx_client:connect(ClientPid) of + {ok, _} -> + emqx_logger:info("[Bridge] connected to remote successfully"), + Subs = subscribe_remote_topics(ClientPid, get_value(subscriptions, Options, [])), + Forwards = subscribe_local_topics(Options), + {NewReplayQ, AckRef, ReadQ} = open_replayq(ReplayQ, QueueOption), + {ok, NewReadQ} = publish_readq_msg(ClientPid, ReadQ, []), + {<<"start bridge successfully">>, + State#state{client_pid = ClientPid, + subscriptions = Subs, + readq = NewReadQ, + replayq = NewReplayQ, + ackref = AckRef, + forwards = Forwards}}; + {error, Reason} -> + emqx_logger:error("[Bridge] connect to remote failed! error: ~p", [Reason]), + {<<"connect to remote failed">>, + State#state{client_pid = ClientPid}} + end; + {error, Reason} -> + emqx_logger:error("[Bridge] ~p failed! error: ~p", [Action, Reason]), + {<<"start bridge failed">>, State} + end. + +open_replayq(undefined, #{batch_size := BatchSize, + replayq_dir := ReplayqDir, + replayq_seg_bytes := ReplayqSegBytes}) -> + ReplayQ = replayq:open(#{dir => ReplayqDir, + seg_bytes => ReplayqSegBytes, + sizer => fun(Term) -> + size(term_to_binary(Term)) + end, + marshaller => fun({PktId, Msg}) -> + term_to_binary({PktId, Msg}); + (Bin) -> + binary_to_term(Bin) + end}), + replayq:pop(ReplayQ, #{count_limit => BatchSize}); +open_replayq(ReplayQ, #{batch_size := BatchSize}) -> + replayq:pop(ReplayQ, #{count_limit => BatchSize}). From 78b3c375d83f89f31998ec6b41466b7ebcea5460 Mon Sep 17 00:00:00 2001 From: YoukiLin <1045735402@qq.com> Date: Thu, 3 Jan 2019 12:59:39 +0800 Subject: [PATCH 07/41] Improving 'emqx_ws_connection' coverage (#2107) --- Makefile | 3 +- src/emqx_connection.erl | 7 +- test/emqx_connection_SUITE.erl | 102 +++++++++++++++++++++++++ test/emqx_protocol_SUITE.erl | 10 +-- test/emqx_tracer_SUITE.erl | 47 ++++++++++++ test/emqx_ws_connection_SUITE.erl | 121 ++++++++++++++++++++++++++++++ 6 files changed, 282 insertions(+), 8 deletions(-) create mode 100644 test/emqx_connection_SUITE.erl create mode 100644 test/emqx_tracer_SUITE.erl create mode 100644 test/emqx_ws_connection_SUITE.erl diff --git a/Makefile b/Makefile index 41cdd5ab8..09b9a4da2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,8 @@ CT_SUITES = emqx emqx_client emqx_zone emqx_banned emqx_session \ emqx_mqtt_props emqx_mqueue emqx_net emqx_pqueue emqx_router emqx_sm \ emqx_tables emqx_time emqx_topic emqx_trie emqx_vm emqx_mountpoint \ emqx_listeners emqx_protocol emqx_pool emqx_shared_sub emqx_bridge \ - emqx_hooks emqx_batch emqx_sequence emqx_pmon emqx_pd emqx_gc + emqx_hooks emqx_batch emqx_sequence emqx_pmon emqx_pd emqx_gc emqx_ws_connection \ + emqx_packet emqx_connection emqx_tracer CT_NODE_NAME = emqxct@127.0.0.1 CT_OPTS = -cover test/ct.cover.spec -erl_args -name $(CT_NODE_NAME) diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 9f2572b32..7eb68e329 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -78,11 +78,14 @@ info(#state{transport = Transport, {sockname, Sockname}, {conn_state, ConnState}, {active_n, ActiveN}, - {rate_limit, esockd_rate_limit:info(RateLimit)}, - {pub_limit, esockd_rate_limit:info(PubLimit)}], + {rate_limit, rate_limit_info(RateLimit)}, + {pub_limit, rate_limit_info(PubLimit)}], ProtoInfo = emqx_protocol:info(ProtoState), lists:usort(lists:append(ConnInfo, ProtoInfo)). +rate_limit_info(undefined) -> #{}; +rate_limit_info(Limit) -> esockd_rate_limit:info(Limit). + %% for dashboard attrs(CPid) when is_pid(CPid) -> call(CPid, attrs); diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl new file mode 100644 index 000000000..1678eab44 --- /dev/null +++ b/test/emqx_connection_SUITE.erl @@ -0,0 +1,102 @@ +%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(emqx_connection_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-include_lib("common_test/include/ct.hrl"). + +-include("emqx_mqtt.hrl"). + +-define(STATS, [{mailbox_len, _}, + {heap_size, _}, + {reductions, _}, + {recv_pkt, _}, + {recv_msg, _}, + {send_pkt, _}, + {send_msg, _}, + {recv_oct, _}, + {recv_cnt, _}, + {send_oct, _}, + {send_cnt, _}, + {send_pend, _}]). + +-define(ATTRS, [{clean_start, _}, + {client_id, _}, + {connected_at, _}, + {is_bridge, _}, + {is_super, _}, + {keepalive, _}, + {mountpoint, _}, + {peercert, _}, + {peername, _}, + {proto_name, _}, + {proto_ver, _}, + {sockname, _}, + {username, _}, + {zone, _}]). + +-define(INFO, [{ack_props, _}, + {active_n, _}, + {clean_start, _}, + {client_id, _}, + {conn_props, _}, + {conn_state, _}, + {connected_at, _}, + {enable_acl, _}, + {is_bridge, _}, + {is_super, _}, + {keepalive, _}, + {mountpoint, _}, + {peercert, _}, + {peername, _}, + {proto_name, _}, + {proto_ver, _}, + {pub_limit, _}, + {rate_limit, _}, + {session, _}, + {sockname, _}, + {socktype, _}, + {topic_aliases, _}, + {username, _}, + {will_msg, _}, + {zone, _}]). + +all() -> + [t_connect_api]. + +init_per_suite(Config) -> + emqx_ct_broker_helpers:run_setup_steps(), + Config. + +end_per_suite(_Config) -> + emqx_ct_broker_helpers:run_teardown_steps(). + +t_connect_api(_Config) -> + {ok, T1} = emqx_client:start_link([{host, "localhost"}, + {client_id, <<"client1">>}, + {username, <<"testuser1">>}, + {password, <<"pass1">>}]), + {ok, _} = emqx_client:connect(T1), + CPid = emqx_cm:lookup_conn_pid(<<"client1">>), + ?STATS = emqx_connection:stats(CPid), + ?ATTRS = emqx_connection:attrs(CPid), + ?INFO = emqx_connection:info(CPid), + SessionPid = emqx_connection:session(CPid), + true = is_pid(SessionPid), + emqx_client:disconnect(T1). diff --git a/test/emqx_protocol_SUITE.erl b/test/emqx_protocol_SUITE.erl index 3bf9976c7..5189a395b 100644 --- a/test/emqx_protocol_SUITE.erl +++ b/test/emqx_protocol_SUITE.erl @@ -28,10 +28,10 @@ -define(TOPICS, [<<"TopicA">>, <<"TopicA/B">>, <<"Topic/C">>, <<"TopicA/C">>, <<"/TopicA">>]). --define(CLIENT2, ?CONNECT_PACKET(#mqtt_packet_connect{ - username = <<"admin">>, - clean_start = false, - password = <<"public">>})). +-define(CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{ + client_id = <<"mqtt_client">>, + username = <<"emqx">>, + password = <<"public">>})). all() -> [ @@ -507,7 +507,6 @@ raw_recv_parse(P, ProtoVersion) -> emqx_frame:parse(P, {none, #{max_packet_size => ?MAX_PACKET_SIZE, version => ProtoVersion}}). - acl_deny_action(_) -> emqx_zone:set_env(external, acl_deny_action, disconnect), process_flag(trap_exit, true), @@ -557,6 +556,7 @@ acl_deny_do_disconnect(publish, QoS, Topic) -> {'EXIT', Client, _Reason} -> false = is_process_alive(Client) end; + acl_deny_do_disconnect(subscribe, QoS, Topic) -> {ok, Client} = emqx_client:start_link([{username, <<"emqx">>}]), {ok, _} = emqx_client:connect(Client), diff --git a/test/emqx_tracer_SUITE.erl b/test/emqx_tracer_SUITE.erl new file mode 100644 index 000000000..b8ec8e21c --- /dev/null +++ b/test/emqx_tracer_SUITE.erl @@ -0,0 +1,47 @@ +%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(emqx_tracer_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-include_lib("common_test/include/ct.hrl"). + +all() -> [start_traces]. + +init_per_suite(Config) -> + emqx_ct_broker_helpers:run_setup_steps(), + Config. + +end_per_suite(_Config) -> + emqx_ct_broker_helpers:run_teardown_steps(). + +start_traces(_Config) -> + {ok, T} = emqx_client:start_link([{host, "localhost"}, + {client_id, <<"client">>}, + {username, <<"testuser">>}, + {password, <<"pass">>}]), + emqx_client:connect(T), + emqx_client:subscribe(T, <<"a/b/c">>), + ok = emqx_tracer:start_trace({client_id, <<"client">>}, all, "test/emqx_SUITE_data/clientid_trace.log"), + ok = emqx_tracer:start_trace({topic, <<"topic">>}, all, "test/emqx_SUITE_data/topic_trace.log"), + {ok, _} = file:read_file("test/emqx_SUITE_data/clientid_trace.log"), + {ok, _} = file:read_file("test/emqx_SUITE_data/topic_trace.log"), + Result = emqx_tracer:lookup_traces(), + ?assertEqual([{{client_id,<<"client">>},{all,"test/emqx_SUITE_data/clientid_trace.log"}},{{topic,<<"topic">>},{all,"test/emqx_SUITE_data/topic_trace.log"}}], Result), + ok = emqx_tracer:stop_trace({client_id, <<"client">>}), + ok = emqx_tracer:stop_trace({topic, <<"topic">>}). diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl new file mode 100644 index 000000000..848e518e1 --- /dev/null +++ b/test/emqx_ws_connection_SUITE.erl @@ -0,0 +1,121 @@ +%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(emqx_ws_connection_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-include_lib("common_test/include/ct.hrl"). + +-include("emqx_mqtt.hrl"). + + +-define(CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{ + client_id = <<"mqtt_client">>, + username = <<"admin">>, + password = <<"public">>})). + +-define(SUBCODE, [0]). + +-define(PACKETID, 1). + +-define(PUBQOS, 1). + +-define(INFO, [{socktype, _}, + {conn_state, _}, + {peername, _}, + {sockname, _}, + {zone, _}, + {client_id, <<"mqtt_client">>}, + {username, <<"admin">>}, + {peername, _}, + {peercert, _}, + {proto_ver, _}, + {proto_name, _}, + {clean_start, _}, + {keepalive, _}, + {mountpoint, _}, + {is_super, _}, + {is_bridge, _}, + {connected_at, _}, + {conn_props, _}, + {ack_props, _}, + {session, _}, + {topic_aliases, _}, + {will_msg, _}, + {enable_acl, _}]). + +-define(ATTRS, [{clean_start,true}, + {client_id, <<"mqtt_client">>}, + {connected_at, _}, + {is_bridge, _}, + {is_super, _}, + {keepalive, _}, + {mountpoint, _}, + {peercert, _}, + {peername, _}, + {proto_name, _}, + {proto_ver, _}, + {sockname, _}, + {username, <<"admin">>}, + {zone, _}]). + +-define(STATS, [{recv_oct, _}, + {recv_cnt, _}, + {send_oct, _}, + {send_cnt, _}, + {mailbox_len, _}, + {heap_size, _}, + {reductions, _}, + {recv_pkt, _}, + {recv_msg, _}, + {send_pkt, _}, + {send_msg, _}]). + +all() -> + [t_ws_connect_api]. + +init_per_suite(Config) -> + emqx_ct_broker_helpers:run_setup_steps(), + Config. + +end_per_suite(_Config) -> + emqx_ct_broker_helpers:run_teardown_steps(). + +t_ws_connect_api(_Config) -> + WS = rfc6455_client:new("ws://127.0.0.1:8083" ++ "/mqtt", self()), + {ok, _} = rfc6455_client:open(WS), + Packet = raw_send_serialize(?CLIENT), + ok = rfc6455_client:send_binary(WS, Packet), + {binary, CONACK} = rfc6455_client:recv(WS), + {ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), _} = raw_recv_pase(CONACK), + Pid = emqx_cm:lookup_conn_pid(<<"mqtt_client">>), + ?INFO = emqx_ws_connection:info(Pid), + ?ATTRS = emqx_ws_connection:attrs(Pid), + ?STATS = emqx_ws_connection:stats(Pid), + SessionPid = emqx_ws_connection:session(Pid), + true = is_pid(SessionPid), + ok = emqx_ws_connection:kick(Pid), + {close, _} = rfc6455_client:close(WS), + ok. + +raw_send_serialize(Packet) -> + emqx_frame:serialize(Packet). + +raw_recv_pase(P) -> + emqx_frame:parse(P, {none, #{max_packet_size => ?MAX_PACKET_SIZE, + version => ?MQTT_PROTO_V4} }). From 69954480bfcd24ed931cf8a8d1eda36517a3f02f Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Thu, 3 Jan 2019 13:54:31 +0800 Subject: [PATCH 08/41] Hook args (#2119) --- etc/emqx.conf | 2 ++ src/emqx_session.erl | 27 ++++++++++++++------------- src/emqx_time.erl | 7 +++++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/etc/emqx.conf b/etc/emqx.conf index a47abddf7..fc37427e6 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -2043,3 +2043,5 @@ sysmon.busy_port = false ## ## Value: true | false sysmon.busy_dist_port = true + +{{ additional_configs }} diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 5d5381f6e..ed33df3ce 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -451,21 +451,21 @@ handle_call(Req, _From, State) -> %% SUBSCRIBE: handle_cast({subscribe, FromPid, {PacketId, _Properties, TopicFilters}}, - State = #state{client_id = ClientId, subscriptions = Subscriptions}) -> + State = #state{client_id = ClientId, username = Username, subscriptions = Subscriptions}) -> {ReasonCodes, Subscriptions1} = lists:foldr(fun({Topic, SubOpts = #{qos := QoS}}, {RcAcc, SubMap}) -> {[QoS|RcAcc], case maps:find(Topic, SubMap) of {ok, SubOpts} -> - emqx_hooks:run('session.subscribed', [#{client_id => ClientId}, Topic, SubOpts#{first => false}]), + emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => false}]), SubMap; {ok, _SubOpts} -> emqx_broker:set_subopts(Topic, SubOpts), %% Why??? - emqx_hooks:run('session.subscribed', [#{client_id => ClientId}, Topic, SubOpts#{first => false}]), + emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => false}]), maps:put(Topic, SubOpts, SubMap); error -> emqx_broker:subscribe(Topic, ClientId, SubOpts), - emqx_hooks:run('session.subscribed', [#{client_id => ClientId}, Topic, SubOpts#{first => true}]), + emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => true}]), maps:put(Topic, SubOpts, SubMap) end} end, {[], Subscriptions}, TopicFilters), @@ -474,13 +474,13 @@ handle_cast({subscribe, FromPid, {PacketId, _Properties, TopicFilters}}, %% UNSUBSCRIBE: handle_cast({unsubscribe, From, {PacketId, _Properties, TopicFilters}}, - State = #state{client_id = ClientId, subscriptions = Subscriptions}) -> + State = #state{client_id = ClientId, username = Username, subscriptions = Subscriptions}) -> {ReasonCodes, Subscriptions1} = lists:foldr(fun({Topic, _SubOpts}, {Acc, SubMap}) -> case maps:find(Topic, SubMap) of {ok, SubOpts} -> ok = emqx_broker:unsubscribe(Topic), - emqx_hooks:run('session.unsubscribed', [#{client_id => ClientId}, Topic, SubOpts]), + emqx_hooks:run('session.unsubscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts]), {[?RC_SUCCESS|Acc], maps:remove(Topic, SubMap)}; error -> {[?RC_NO_SUBSCRIPTION_EXISTED|Acc], SubMap} @@ -654,11 +654,12 @@ handle_info(Info, State) -> terminate(Reason, #state{will_msg = WillMsg, client_id = ClientId, + username = Username, conn_pid = ConnPid, old_conn_pid = OldConnPid}) -> send_willmsg(WillMsg), [maybe_shutdown(Pid, Reason) || Pid <- [ConnPid, OldConnPid]], - emqx_hooks:run('session.terminated', [#{client_id => ClientId}, Reason]). + emqx_hooks:run('session.terminated', [#{client_id => ClientId, username => Username}, Reason]). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -823,8 +824,8 @@ run_dispatch_steps([{subid, SubId}|Steps], Msg, State) -> run_dispatch_steps(Steps, emqx_message:set_header('Subscription-Identifier', SubId, Msg), State). %% Enqueue message if the client has been disconnected -dispatch(Msg, State = #state{client_id = ClientId, conn_pid = undefined}) -> - case emqx_hooks:run('message.dropped', [#{client_id => ClientId}, Msg]) of +dispatch(Msg, State = #state{client_id = ClientId, username = Username, conn_pid = undefined}) -> + case emqx_hooks:run('message.dropped', [#{client_id => ClientId, username => Username}, Msg]) of ok -> enqueue_msg(Msg, State); stop -> State end; @@ -888,20 +889,20 @@ await(PacketId, Msg, State = #state{inflight = Inflight}) -> PacketId, {publish, {PacketId, Msg}, os:timestamp()}, Inflight), ensure_retry_timer(State#state{inflight = Inflight1}). -acked(puback, PacketId, State = #state{client_id = ClientId, inflight = Inflight}) -> +acked(puback, PacketId, State = #state{client_id = ClientId, username = Username, inflight = Inflight}) -> case emqx_inflight:lookup(PacketId, Inflight) of {value, {publish, {_, Msg}, _Ts}} -> - emqx_hooks:run('message.acked', [#{client_id => ClientId}], Msg), + emqx_hooks:run('message.acked', [#{client_id => ClientId, username => Username}], Msg), State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}; none -> ?LOG(warning, "Duplicated PUBACK PacketId ~w", [PacketId], State), State end; -acked(pubrec, PacketId, State = #state{client_id = ClientId, inflight = Inflight}) -> +acked(pubrec, PacketId, State = #state{client_id = ClientId, username = Username, inflight = Inflight}) -> case emqx_inflight:lookup(PacketId, Inflight) of {value, {publish, {_, Msg}, _Ts}} -> - emqx_hooks:run('message.acked', [#{client_id => ClientId}], Msg), + emqx_hooks:run('message.acked', [#{client_id => ClientId, username => Username}], Msg), State#state{inflight = emqx_inflight:update(PacketId, {pubrel, PacketId, os:timestamp()}, Inflight)}; {value, {pubrel, PacketId, _Ts}} -> ?LOG(warning, "Duplicated PUBREC PacketId ~w", [PacketId], State), diff --git a/src/emqx_time.erl b/src/emqx_time.erl index 95bfc9409..a7edf7efb 100644 --- a/src/emqx_time.erl +++ b/src/emqx_time.erl @@ -14,7 +14,7 @@ -module(emqx_time). --export([seed/0, now_secs/0, now_secs/1, now_ms/0, now_ms/1]). +-export([seed/0, now_secs/0, now_secs/1, now_ms/0, now_ms/1, ts_from_ms/1]). seed() -> rand:seed(exsplus, erlang:timestamp()). @@ -29,4 +29,7 @@ now_ms() -> erlang:system_time(millisecond). now_ms({MegaSecs, Secs, MicroSecs}) -> - (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). + (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). + +ts_from_ms(Ms) -> + {Ms div 1000000, Ms rem 1000000, 0}. From 3b8eb413475ece8e347280eb4a0b86949700da30 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Thu, 3 Jan 2019 18:23:33 +0800 Subject: [PATCH 09/41] Remove the log header macro --- include/logger.hrl | 19 +++---------------- src/emqx_connection.erl | 3 ++- src/emqx_protocol.erl | 4 ++-- src/emqx_session.erl | 37 +++++++++++++++++++------------------ src/emqx_ws_connection.erl | 4 ++-- 5 files changed, 28 insertions(+), 39 deletions(-) diff --git a/include/logger.hrl b/include/logger.hrl index db93d7f56..71f1a3bb6 100644 --- a/include/logger.hrl +++ b/include/logger.hrl @@ -1,23 +1,10 @@ %%-------------------------------------------------------------------- -%% Logs with a header prefixed to the log message. -%% And the log args are puted into report_cb for lazy evaluation. +%% The args are put into report_cb for lazy evaluation. %%-------------------------------------------------------------------- --ifdef(LOG_HEADER). -%% with header --define(LOG(Level, Format, Args), - begin - (logger:log(Level,#{},#{report_cb => - fun(_) -> - {?LOG_HEADER ++ " "++ (Format), (Args)} - end})) - end). --else. -%% without header --define(LOG(Level, Format, Args), +-define(LOG_LZ(Level, Format, Args), begin (logger:log(Level,#{},#{report_cb => fun(_) -> {(Format), (Args)} end})) - end). --endif. \ No newline at end of file + end). \ No newline at end of file diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 7eb68e329..47162b009 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -19,7 +19,6 @@ -include("emqx.hrl"). -include("emqx_mqtt.hrl"). --define(LOG_HEADER, "[MQTT]"). -include("logger.hrl"). -export([start_link/3]). @@ -53,6 +52,8 @@ -define(DEFAULT_ACTIVE_N, 100). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). +-define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[MQTT] " ++ Format, Args)). + start_link(Transport, Socket, Options) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Transport, Socket, Options]])}. diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index 13eff95bf..f76d2a322 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -14,8 +14,6 @@ -module(emqx_protocol). --define(LOG_HEADER, "[MQTT]"). - -include("emqx.hrl"). -include("emqx_mqtt.hrl"). -include("logger.hrl"). @@ -79,6 +77,8 @@ -define(NO_PROPS, undefined). +-define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[MQTT] " ++ Format, Args)). + %%------------------------------------------------------------------------------ %% Init %%------------------------------------------------------------------------------ diff --git a/src/emqx_session.erl b/src/emqx_session.erl index ed33df3ce..8375a6e02 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -43,6 +43,8 @@ -include("emqx.hrl"). -include("emqx_mqtt.hrl"). +-include("logger.hrl"). + -export([start_link/1]). -export([info/1, attrs/1]). -export([stats/1]). @@ -155,8 +157,7 @@ -export_type([attr/0]). --define(LOG(Level, Format, Args, _State), - emqx_logger:Level("[Session] " ++ Format, Args)). +-define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[Session] " ++ Format, Args)). %% @doc Start a session proc. -spec(start_link(SessAttrs :: map()) -> {ok, pid()}). @@ -390,11 +391,11 @@ handle_call(stats, _From, State) -> reply(stats(State), State); handle_call({discard, ByPid}, _From, State = #state{conn_pid = undefined}) -> - ?LOG(warning, "Discarded by ~p", [ByPid], State), + ?LOG(warning, "Discarded by ~p", [ByPid]), {stop, {shutdown, discarded}, ok, State}; handle_call({discard, ByPid}, _From, State = #state{client_id = ClientId, conn_pid = ConnPid}) -> - ?LOG(warning, "Conn ~p is discarded by ~p", [ConnPid, ByPid], State), + ?LOG(warning, "Conn ~p is discarded by ~p", [ConnPid, ByPid]), ConnPid ! {shutdown, discard, {ClientId, ByPid}}, {stop, {shutdown, discarded}, ok, State}; @@ -413,7 +414,7 @@ handle_call({register_publish_packet_id, PacketId, Ts}, _From, {ok, ensure_stats_timer(ensure_await_rel_timer(State1))} end; true -> - ?LOG(warning, "Dropped qos2 packet ~w for too many awaiting_rel", [PacketId], State), + ?LOG(warning, "Dropped qos2 packet ~w for too many awaiting_rel", [PacketId]), emqx_metrics:trans(inc, 'messages/qos2/dropped'), {{error, ?RC_RECEIVE_MAXIMUM_EXCEEDED}, State} end); @@ -425,7 +426,7 @@ handle_call({pubrec, PacketId, _ReasonCode}, _From, State = #state{inflight = In true -> {ok, ensure_stats_timer(acked(pubrec, PacketId, State))}; false -> - ?LOG(warning, "The PUBREC PacketId ~w is not found.", [PacketId], State), + ?LOG(warning, "The PUBREC PacketId ~w is not found.", [PacketId]), emqx_metrics:trans(inc, 'packets/pubrec/missed'), {{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}, State} end); @@ -437,7 +438,7 @@ handle_call({pubrel, PacketId, _ReasonCode}, _From, State = #state{awaiting_rel {_Ts, AwaitingRel1} -> {ok, ensure_stats_timer(State#state{awaiting_rel = AwaitingRel1})}; error -> - ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId], State), + ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]), emqx_metrics:trans(inc, 'packets/pubrel/missed'), {{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}, State} end); @@ -496,7 +497,7 @@ handle_cast({puback, PacketId, _ReasonCode}, State = #state{inflight = Inflight} true -> ensure_stats_timer(dequeue(acked(puback, PacketId, State))); false -> - ?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId], State), + ?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]), emqx_metrics:trans(inc, 'packets/puback/missed'), State end); @@ -508,7 +509,7 @@ handle_cast({pubcomp, PacketId, _ReasonCode}, State = #state{inflight = Inflight true -> ensure_stats_timer(dequeue(acked(pubcomp, PacketId, State))); false -> - ?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId], State), + ?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]), emqx_metrics:trans(inc, 'packets/pubcomp/missed'), State end); @@ -526,14 +527,14 @@ handle_cast({resume, #{conn_pid := ConnPid, expiry_timer = ExpireTimer, will_delay_timer = WillDelayTimer}) -> - ?LOG(info, "Resumed by connection ~p ", [ConnPid], State), + ?LOG(info, "Resumed by connection ~p ", [ConnPid]), %% Cancel Timers lists:foreach(fun emqx_misc:cancel_timer/1, [RetryTimer, AwaitTimer, ExpireTimer, WillDelayTimer]), case kick(ClientId, OldConnPid, ConnPid) of - ok -> ?LOG(warning, "Connection ~p kickout ~p", [ConnPid, OldConnPid], State); + ok -> ?LOG(warning, "Connection ~p kickout ~p", [ConnPid, OldConnPid]); ignore -> ok end, @@ -614,12 +615,12 @@ handle_info({timeout, Timer, emit_stats}, GcState1 = emqx_gc:reset(GcState), {noreply, NewState#state{gc_state = GcState1}, hibernate}; {shutdown, Reason} -> - ?LOG(warning, "shutdown due to ~p", [Reason], NewState), + ?LOG(warning, "shutdown due to ~p", [Reason]), shutdown(Reason, NewState) end; handle_info({timeout, Timer, expired}, State = #state{expiry_timer = Timer}) -> - ?LOG(info, "expired, shutdown now.", [], State), + ?LOG(info, "expired, shutdown now.", []), shutdown(expired, State); handle_info({timeout, Timer, will_delay}, State = #state{will_msg = WillMsg, will_delay_timer = Timer}) -> @@ -645,7 +646,7 @@ handle_info({'EXIT', OldPid, _Reason}, State = #state{old_conn_pid = OldPid}) -> handle_info({'EXIT', Pid, Reason}, State = #state{conn_pid = ConnPid}) -> ?LOG(error, "Unexpected EXIT: conn_pid=~p, exit_pid=~p, reason=~p", - [ConnPid, Pid, Reason], State), + [ConnPid, Pid, Reason]), {noreply, State}; handle_info(Info, State) -> @@ -780,7 +781,7 @@ expire_awaiting_rel([{PacketId, Ts} | More], Now, case (timer:now_diff(Now, Ts) div 1000) of Age when Age >= Timeout -> emqx_metrics:trans(inc, 'messages/qos2/expired'), - ?LOG(warning, "Dropped qos2 packet ~s for await_rel_timeout", [PacketId], State), + ?LOG(warning, "Dropped qos2 packet ~s for await_rel_timeout", [PacketId]), expire_awaiting_rel(More, Now, State#state{awaiting_rel = maps:remove(PacketId, AwaitingRel)}); Age -> ensure_await_rel_timer(Timeout - max(0, Age), State) @@ -895,7 +896,7 @@ acked(puback, PacketId, State = #state{client_id = ClientId, username = Username emqx_hooks:run('message.acked', [#{client_id => ClientId, username => Username}], Msg), State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}; none -> - ?LOG(warning, "Duplicated PUBACK PacketId ~w", [PacketId], State), + ?LOG(warning, "Duplicated PUBACK PacketId ~w", [PacketId]), State end; @@ -905,10 +906,10 @@ acked(pubrec, PacketId, State = #state{client_id = ClientId, username = Username emqx_hooks:run('message.acked', [#{client_id => ClientId, username => Username}], Msg), State#state{inflight = emqx_inflight:update(PacketId, {pubrel, PacketId, os:timestamp()}, Inflight)}; {value, {pubrel, PacketId, _Ts}} -> - ?LOG(warning, "Duplicated PUBREC PacketId ~w", [PacketId], State), + ?LOG(warning, "Duplicated PUBREC PacketId ~w", [PacketId]), State; none -> - ?LOG(warning, "Unexpected PUBREC PacketId ~w", [PacketId], State), + ?LOG(warning, "Unexpected PUBREC PacketId ~w", [PacketId]), State end; diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 74ef32c0a..df23f50ca 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -14,8 +14,6 @@ -module(emqx_ws_connection). --define(LOG_HEADER, "[WS]"). - -include("emqx.hrl"). -include("emqx_mqtt.hrl"). -include("logger.hrl"). @@ -48,6 +46,8 @@ -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). +-define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[MQTT-WS] " ++ Format, Args)). + %%------------------------------------------------------------------------------ %% API %%------------------------------------------------------------------------------ From d7254cdc7f08811c985dcb289b08764ff0436277 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 4 Jan 2019 17:17:43 +0800 Subject: [PATCH 10/41] Add more macros for logger --- include/logger.hrl | 50 +++++++++++++++++++++++++++++++------- src/emqx_connection.erl | 3 --- src/emqx_protocol.erl | 2 -- src/emqx_session.erl | 3 --- src/emqx_ws_connection.erl | 2 -- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/include/logger.hrl b/include/logger.hrl index 71f1a3bb6..6a94d6bc9 100644 --- a/include/logger.hrl +++ b/include/logger.hrl @@ -1,10 +1,42 @@ -%%-------------------------------------------------------------------- -%% The args are put into report_cb for lazy evaluation. -%%-------------------------------------------------------------------- --define(LOG_LZ(Level, Format, Args), +%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +%% debug | info | notice | warning | error | critical | alert | emergency + +-define(DEBUG(Format), ?LOG(debug, Format, [])). +-define(DEBUG(Format, Args), ?LOG(debug, Format, Args)). + +-define(INFO(Format), ?LOG(info, Format, [])). +-define(INFO(Format, Args), ?LOG(info, Format, Args)). + +-define(NOTICE(Format), ?LOG(notice, Format, [])). +-define(NOTICE(Format, Args), ?LOG(notice, Format, Args)). + +-define(WARN(Format), ?LOG(warning, Format, [])). +-define(WARN(Format, Args), ?LOG(warning, Format, [])). + +-define(ERROR(Format), ?LOG(error, Format, [])). +-define(ERROR(Format, Args), ?LOG(error, Format, Args)). + +-define(CRITICAL(Format), ?LOG(critical, Format, [])). +-define(CRITICAL(Format, Args), ?LOG(critical, Format, Args)). + +-define(ALERT(Format), ?LOG(alert, Format, [])). +-define(ALERT(Format, Args), ?LOG(alert, Format, Args)). + +-define(LOG(Level, Format, Args), begin - (logger:log(Level,#{},#{report_cb => - fun(_) -> - {(Format), (Args)} - end})) - end). \ No newline at end of file + (logger:log(Level,#{},#{report_cb => fun(_) -> {(Format), (Args)} end})) + end). + diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 47162b009..8859a7152 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -18,7 +18,6 @@ -include("emqx.hrl"). -include("emqx_mqtt.hrl"). - -include("logger.hrl"). -export([start_link/3]). @@ -52,8 +51,6 @@ -define(DEFAULT_ACTIVE_N, 100). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). --define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[MQTT] " ++ Format, Args)). - start_link(Transport, Socket, Options) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Transport, Socket, Options]])}. diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index f76d2a322..19d1d9d09 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -77,8 +77,6 @@ -define(NO_PROPS, undefined). --define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[MQTT] " ++ Format, Args)). - %%------------------------------------------------------------------------------ %% Init %%------------------------------------------------------------------------------ diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 8375a6e02..966ff738d 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -42,7 +42,6 @@ -include("emqx.hrl"). -include("emqx_mqtt.hrl"). - -include("logger.hrl"). -export([start_link/1]). @@ -157,8 +156,6 @@ -export_type([attr/0]). --define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[Session] " ++ Format, Args)). - %% @doc Start a session proc. -spec(start_link(SessAttrs :: map()) -> {ok, pid()}). start_link(SessAttrs) -> diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index df23f50ca..69a3849fe 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -46,8 +46,6 @@ -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). --define(LOG(Level, Format, Args), ?LOG_LZ(Level, "[MQTT-WS] " ++ Format, Args)). - %%------------------------------------------------------------------------------ %% API %%------------------------------------------------------------------------------ From 99f428345873dbfc7424f1ccbefc5d772b0b15e6 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 4 Jan 2019 22:55:58 +0800 Subject: [PATCH 11/41] Update cuttlefish to v2.2.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 09b9a4da2..7ddc05d9f 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ NO_AUTOPATCH = cuttlefish ERLC_OPTS += +debug_info -DAPPLICATION=emqx BUILD_DEPS = cuttlefish -dep_cuttlefish = git-emqx https://github.com/emqx/cuttlefish v2.2.0 +dep_cuttlefish = git-emqx https://github.com/emqx/cuttlefish v2.2.1 #TEST_DEPS = emqx_ct_helplers #dep_emqx_ct_helplers = git git@github.com:emqx/emqx-ct-helpers From 9f83544fceeb6d2e36ef584718bad1c456aaef9e Mon Sep 17 00:00:00 2001 From: Gilbert Date: Sun, 6 Jan 2019 19:53:01 +0800 Subject: [PATCH 12/41] Fix failed testcase (#2127) * Fix failed test case Prior to this change, terry-xiaoyu enhanced the cuttlefish to support emqx enterprise edtion configuration entries. But it cause 6 test case failed. * Fix dep-vsn-check --- rebar.config | 2 +- test/emqx_protocol_SUITE.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index 8fa9663ae..993790aa9 100644 --- a/rebar.config +++ b/rebar.config @@ -9,7 +9,7 @@ {ekka, "v0.5.1"}, {replayq, "v0.1.1"}, {esockd, "v5.4.3"}, - {cuttlefish, "v2.2.0"} + {cuttlefish, "v2.2.1"} ]}. {edoc_opts, [{preprocess, true}]}. diff --git a/test/emqx_protocol_SUITE.erl b/test/emqx_protocol_SUITE.erl index 5189a395b..571019a15 100644 --- a/test/emqx_protocol_SUITE.erl +++ b/test/emqx_protocol_SUITE.erl @@ -61,7 +61,7 @@ init_per_suite(Config) -> [start_apps(App, SchemaFile, ConfigFile) || {App, SchemaFile, ConfigFile} <- [{emqx, deps_path(emqx, "priv/emqx.schema"), - deps_path(emqx, "etc/emqx.conf")}]], + deps_path(emqx, "etc/gen.emqx.conf")}]], emqx_zone:set_env(external, max_topic_alias, 20), Config. From cb93a356a07c9b60c836d3487a768050f6ab84c6 Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Mon, 7 Jan 2019 11:40:05 +0800 Subject: [PATCH 13/41] Fix crash if peer closed the connection (#2120) Prior to this fix, we'll get the following crash if we connected to another emqx broker but got refused because of wrong username or password. --- src/emqx_client.erl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/emqx_client.erl b/src/emqx_client.erl index 6fbb29724..015bcc51b 100644 --- a/src/emqx_client.erl +++ b/src/emqx_client.erl @@ -715,7 +715,17 @@ waiting_for_connack(timeout, _Timeout, State) -> end; waiting_for_connack(EventType, EventContent, State) -> - handle_event(EventType, EventContent, waiting_for_connack, State). + case take_call(connect, State) of + {value, #call{from = From}, _State} -> + case handle_event(EventType, EventContent, waiting_for_connack, State) of + {stop, Reason, State} -> + Reply = {error, {Reason, EventContent}}, + {stop_and_reply, Reason, [{reply, From, Reply}]}; + StateCallbackResult -> + StateCallbackResult + end; + false -> {stop, connack_timeout} + end. connected({call, From}, subscriptions, State = #state{subscriptions = Subscriptions}) -> {keep_state, State, [{reply, From, maps:to_list(Subscriptions)}]}; @@ -999,8 +1009,8 @@ handle_event(info, {Closed, _Sock}, _StateName, State) when Closed =:= tcp_closed; Closed =:= ssl_closed -> {stop, {shutdown, Closed}, State}; -handle_event(info, {'EXIT', Owner, Reason}, _, #state{owner = Owner}) -> - {stop, Reason}; +handle_event(info, {'EXIT', Owner, Reason}, _, State = #state{owner = Owner}) -> + {stop, Reason, State}; handle_event(info, {inet_reply, _Sock, ok}, _, State) -> {keep_state, State}; From 0d9929fdaa841087b205299f14ead4d46bc3db4e Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Mon, 7 Jan 2019 11:40:54 +0800 Subject: [PATCH 14/41] Logger level in one command (#2115) * Refactor logger-level-in-one-command * Update API --- src/emqx_logger.erl | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/emqx_logger.erl b/src/emqx_logger.erl index b3c9e9d54..72ba9d8d1 100644 --- a/src/emqx_logger.erl +++ b/src/emqx_logger.erl @@ -27,6 +27,7 @@ -export([get_primary_log_level/0, set_primary_log_level/1]). -export([get_log_handlers/0, get_log_handler/1, set_log_handler_level/2]). +-export([set_log_level/1]). debug(Msg) -> logger:debug(Msg). @@ -89,6 +90,13 @@ get_log_handler(HandlerId) -> set_log_handler_level(HandlerId, Level) -> logger:set_handler_config(HandlerId, level, Level). +%% Set both the primary and all handlers level in one command +set_log_level(Level) -> + case set_primary_log_level(Level) of + ok -> set_all_log_handlers_level(Level); + {error, Error} -> {error, {primary_logger_level, Error}} + end. + %%======================== %% Internal Functions %%======================== @@ -107,4 +115,23 @@ log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h, config := #{file := Filename}}) -> {Id, Level, Filename}; log_hanlder_info(#{id := Id, level := Level, module := _OtherModule}) -> - {Id, Level, unknown}. \ No newline at end of file + {Id, Level, unknown}. + +%% set level for all log handlers in one command +set_all_log_handlers_level(Level) -> + set_all_log_handlers_level(get_log_handlers(), Level, []). + +set_all_log_handlers_level([{ID, Level, _Dst} | List], NewLevel, ChangeHistory) -> + case set_log_handler_level(ID, NewLevel) of + ok -> set_all_log_handlers_level(List, NewLevel, [{ID, Level} | ChangeHistory]); + {error, Error} -> + rollback(ChangeHistory), + {error, {handlers_logger_level, {ID, Error}}} + end; +set_all_log_handlers_level([], _NewLevel, _NewHanlder) -> + ok. + +rollback([{ID, Level} | List]) -> + emqx_logger:set_log_handler_level(ID, Level), + rollback(List); +rollback([]) -> ok. \ No newline at end of file From dee88fb01802b5ce16e41ca75f2c8424a1812eba Mon Sep 17 00:00:00 2001 From: Gilbert Date: Mon, 7 Jan 2019 14:15:27 +0800 Subject: [PATCH 15/41] Fix mem cache bug (#2129) --- src/emqx_bridge.erl | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index df2c3ff07..133b3b621 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -368,6 +368,18 @@ publish_readq_msg(ClientPid, [{_PktId, Msg} | ReadQ], NewReadQ) -> {ok, PktId} = emqx_client:publish(ClientPid, Msg), publish_readq_msg(ClientPid, ReadQ, [{PktId, Msg} | NewReadQ]). +delete(PktId, State = #state{ replayq = ReplayQ, + queue_option = #{ mem_cache := false }}) -> + {NewReplayQ, NewAckRef, Msgs} = replayq:pop(ReplayQ, #{count_limit => 1}), + case Msgs of + [{PktId, Msg}] -> + logger:debug("[Msg] PacketId ~p, Msg: ~p", [PktId, Msg]), + replayq:ack(ReplayQ, NewAckRef), + State#state{ replayq = NewReplayQ, ackref = NewAckRef}; + _ -> + self() ! pop, + State + end; delete(_PktId, State = #state{readq = [], writeq = [], replayq = ReplayQ, ackref = AckRef}) -> ok = replayq:ack(ReplayQ, AckRef), self() ! pop, @@ -380,8 +392,11 @@ delete(PktId, State = #state{readq = ReadQ}) -> State#state{readq = lists:keydelete(PktId, 1, ReadQ)}. bridge(Action, State = #state{options = Options, - replayq = ReplayQ, - queue_option = QueueOption}) -> + replayq = ReplayQ, + queue_option + = QueueOption + = #{batch_size := BatchSize}}) + when BatchSize > 0 -> case emqx_client:start_link([{owner, self()}|options(Options)]) of {ok, ClientPid} -> case emqx_client:connect(ClientPid) of @@ -406,7 +421,10 @@ bridge(Action, State = #state{options = Options, {error, Reason} -> emqx_logger:error("[Bridge] ~p failed! error: ~p", [Action, Reason]), {<<"start bridge failed">>, State} - end. + end; +bridge(Action, State) -> + emqx_logger:error("[Bridge] ~p failed! error: batch_size should greater than zero", [Action]), + {<<"Open Replayq failed">>, State}. open_replayq(undefined, #{batch_size := BatchSize, replayq_dir := ReplayqDir, From 981d785c06e74798608161c35ead7ef3758c6b3d Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Jan 2019 10:14:48 +0800 Subject: [PATCH 16/41] Fix data type of mountpoint from string to binary --- priv/emqx.schema | 2 ++ 1 file changed, 2 insertions(+) diff --git a/priv/emqx.schema b/priv/emqx.schema index 134931016..d568e6c66 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -861,6 +861,8 @@ end}. maps:put(iolist_to_binary(Topic), P, Acc) end, string:tokens(Val, ",")) end; + ("mountpoint", Val) -> + {mountpoint, iolist_to_binary(Val)}; (Opt, Val) -> {list_to_atom(Opt), Val} end, From 0c418581b27267ec7db387fb7bf0b3e5cebe495e Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 24 Dec 2018 13:08:20 +0800 Subject: [PATCH 17/41] Add emqx_trie:empty/0 and optimize match_routes/1 --- src/emqx_router.erl | 10 +++++++--- src/emqx_trie.erl | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/emqx_router.erl b/src/emqx_router.erl index 0463526d6..793735d55 100644 --- a/src/emqx_router.erl +++ b/src/emqx_router.erl @@ -101,9 +101,13 @@ do_add_route(Topic, Dest) when is_binary(Topic) -> %% @doc Match routes -spec(match_routes(emqx_topic:topic()) -> [emqx_types:route()]). match_routes(Topic) when is_binary(Topic) -> - %% Optimize: routing table will be replicated to all router nodes. - Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), - lists:append([lookup_routes(To) || To <- [Topic | Matched]]). + case emqx_trie:empty() of + true -> lookup_routes(Topic); + false -> + %% Optimize: routing table will be replicated to all router nodes. + Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), + lists:append([lookup_routes(To) || To <- [Topic | Matched]]) + end. -spec(lookup_routes(emqx_topic:topic()) -> [emqx_types:route()]). lookup_routes(Topic) -> diff --git a/src/emqx_trie.erl b/src/emqx_trie.erl index 27ff52827..a892b1fc2 100644 --- a/src/emqx_trie.erl +++ b/src/emqx_trie.erl @@ -24,6 +24,7 @@ %% Trie APIs -export([insert/1, match/1, lookup/1, delete/1]). +-export([empty/0]). %% Mnesia tables -define(TRIE, emqx_trie). @@ -100,6 +101,11 @@ delete(Topic) when is_binary(Topic) -> [] -> ok end. +%% @doc Is the trie empty? +-spec(empty() -> boolean()). +empty() -> + ets:info(?TRIE, size) == 0. + %%------------------------------------------------------------------------------ %% Internal functions %%------------------------------------------------------------------------------ From 4230a3c535a5b2fb13d11507699f25de7f306cc5 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 4 Jan 2019 17:49:55 +0800 Subject: [PATCH 18/41] Optimize the trie match --- src/emqx_router.erl | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/emqx_router.erl b/src/emqx_router.erl index 793735d55..f01db0d52 100644 --- a/src/emqx_router.erl +++ b/src/emqx_router.erl @@ -101,14 +101,20 @@ do_add_route(Topic, Dest) when is_binary(Topic) -> %% @doc Match routes -spec(match_routes(emqx_topic:topic()) -> [emqx_types:route()]). match_routes(Topic) when is_binary(Topic) -> - case emqx_trie:empty() of - true -> lookup_routes(Topic); - false -> - %% Optimize: routing table will be replicated to all router nodes. - Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), + case match_trie(Topic) of + [] -> lookup_routes(Topic); + Matched -> lists:append([lookup_routes(To) || To <- [Topic | Matched]]) end. +%% @private +%% Optimize: routing table will be replicated to all router nodes. +match_trie(Topic) -> + case emqx_trie:empty() of + true -> []; + false -> mnesia:ets(fun emqx_trie:match/1, [Topic]) + end. + -spec(lookup_routes(emqx_topic:topic()) -> [emqx_types:route()]). lookup_routes(Topic) -> ets:lookup(?ROUTE, Topic). From d27c824ed8f411479dd69370417d8efc9234dcd7 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 4 Jan 2019 18:09:40 +0800 Subject: [PATCH 19/41] Add test case for emqx_trie:empty/0 --- test/emqx_trie_SUITE.erl | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/test/emqx_trie_SUITE.erl b/test/emqx_trie_SUITE.erl index 500fe3574..043ce95f1 100644 --- a/test/emqx_trie_SUITE.erl +++ b/test/emqx_trie_SUITE.erl @@ -24,7 +24,7 @@ -define(TRIE_TABS, [emqx_trie, emqx_trie_node]). all() -> - [t_mnesia, t_insert, t_match, t_match2, t_match3, t_delete, t_delete2, t_delete3]. + [t_mnesia, t_insert, t_match, t_match2, t_match3, t_empty, t_delete, t_delete2, t_delete3]. init_per_suite(Config) -> application:load(emqx), @@ -58,7 +58,7 @@ t_insert(_) -> ?TRIE:insert(<<"sensor">>), ?TRIE:lookup(<<"sensor">>) end, - ?assertEqual({atomic, [TN]}, mnesia:transaction(Fun)). + ?assertEqual({atomic, [TN]}, trans(Fun)). t_match(_) -> Machted = [<<"sensor/+/#">>, <<"sensor/#">>], @@ -68,7 +68,7 @@ t_match(_) -> ?TRIE:insert(<<"sensor/#">>), ?TRIE:match(<<"sensor/1">>) end, - ?assertEqual({atomic, Machted}, mnesia:transaction(Fun)). + ?assertEqual({atomic, Machted}, trans(Fun)). t_match2(_) -> Matched = {[<<"+/+/#">>, <<"+/#">>, <<"#">>], []}, @@ -79,16 +79,23 @@ t_match2(_) -> {?TRIE:match(<<"a/b/c">>), ?TRIE:match(<<"$SYS/broker/zenmq">>)} end, - ?assertEqual({atomic, Matched}, mnesia:transaction(Fun)). + ?assertEqual({atomic, Matched}, trans(Fun)). t_match3(_) -> Topics = [<<"d/#">>, <<"a/b/c">>, <<"a/b/+">>, <<"a/#">>, <<"#">>, <<"$SYS/#">>], - mnesia:transaction(fun() -> [emqx_trie:insert(Topic) || Topic <- Topics] end), + trans(fun() -> [emqx_trie:insert(Topic) || Topic <- Topics] end), Matched = mnesia:async_dirty(fun emqx_trie:match/1, [<<"a/b/c">>]), ?assertEqual(4, length(Matched)), SysMatched = mnesia:async_dirty(fun emqx_trie:match/1, [<<"$SYS/a/b/c">>]), ?assertEqual([<<"$SYS/#">>], SysMatched). +t_empty(_) -> + ?assert(?TRIE:empty()), + trans(fun ?TRIE:insert/1, [<<"topic/x/#">>]), + ?assertNot(?TRIE:empty()), + trans(fun ?TRIE:delete/1, [<<"topic/x/#">>]), + ?assert(?TRIE:empty()). + t_delete(_) -> TN = #trie_node{node_id = <<"sensor/1">>, edge_count = 2, @@ -103,7 +110,7 @@ t_delete(_) -> ?TRIE:delete(<<"sensor/1/metric">>), ?TRIE:lookup(<<"sensor/1">>) end, - ?assertEqual({atomic, [TN]}, mnesia:transaction(Fun)). + ?assertEqual({atomic, [TN]}, trans(Fun)). t_delete2(_) -> Fun = fun() -> @@ -115,7 +122,7 @@ t_delete2(_) -> ?TRIE:delete(<<"sensor/1/metric/3">>), {?TRIE:lookup(<<"sensor">>), ?TRIE:lookup(<<"sensor/1">>)} end, - ?assertEqual({atomic, {[], []}}, mnesia:transaction(Fun)). + ?assertEqual({atomic, {[], []}}, trans(Fun)). t_delete3(_) -> Fun = fun() -> @@ -129,8 +136,13 @@ t_delete3(_) -> ?TRIE:delete(<<"sensor/+/unknown">>), {?TRIE:lookup(<<"sensor">>), ?TRIE:lookup(<<"sensor/+">>)} end, - ?assertEqual({atomic, {[], []}}, mnesia:transaction(Fun)). + ?assertEqual({atomic, {[], []}}, trans(Fun)). clear_tables() -> lists:foreach(fun mnesia:clear_table/1, ?TRIE_TABS). +trans(Fun) -> + mnesia:transaction(Fun). +trans(Fun, Args) -> + mnesia:transaction(Fun, Args). + From 52241a78048d84b2ce067948787505623bd0e7f8 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 9 Jan 2019 11:48:52 +0800 Subject: [PATCH 20/41] Add more test cases for code coverage --- test/emqx_router_SUITE.erl | 29 +++++++++++++++++++++++------ test/emqx_trie_SUITE.erl | 5 +++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/test/emqx_router_SUITE.erl b/test/emqx_router_SUITE.erl index c115fd0cd..439866899 100644 --- a/test/emqx_router_SUITE.erl +++ b/test/emqx_router_SUITE.erl @@ -27,10 +27,13 @@ all() -> groups() -> [{route, [sequence], - [t_add_delete, + [t_mnesia, + t_add_delete, t_do_add_delete, t_match_routes, - t_has_routes]}]. + t_print_routes, + t_has_routes, + t_unexpected]}]. init_per_suite(Config) -> emqx_ct_broker_helpers:run_setup_steps(), @@ -46,18 +49,21 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, _Config) -> clear_tables(). +t_mnesia(_) -> + %% for coverage + ok = emqx_router:mnesia(copy). + t_add_delete(_) -> - ?R:add_route(<<"a/b/c">>, node()), + ?R:add_route(<<"a/b/c">>), ?R:add_route(<<"a/b/c">>, node()), ?R:add_route(<<"a/+/b">>, node()), ?assertEqual([<<"a/+/b">>, <<"a/b/c">>], lists:sort(?R:topics())), - ?R:delete_route(<<"a/b/c">>), ?R:delete_route(<<"a/+/b">>, node()), ?assertEqual([], ?R:topics()). t_do_add_delete(_) -> - ?R:do_add_route(<<"a/b/c">>, node()), + ?R:do_add_route(<<"a/b/c">>), ?R:do_add_route(<<"a/b/c">>, node()), ?R:do_add_route(<<"a/+/b">>, node()), ?assertEqual([<<"a/+/b">>, <<"a/b/c">>], lists:sort(?R:topics())), @@ -67,7 +73,7 @@ t_do_add_delete(_) -> ?assertEqual([], ?R:topics()). t_match_routes(_) -> - ?R:add_route(<<"a/b/c">>, node()), + ?R:add_route(<<"a/b/c">>), ?R:add_route(<<"a/+/c">>, node()), ?R:add_route(<<"a/b/#">>, node()), ?R:add_route(<<"#">>, node()), @@ -82,11 +88,22 @@ t_match_routes(_) -> ?R:delete_route(<<"#">>, node()), ?assertEqual([], lists:sort(?R:match_routes(<<"a/b/c">>))). +t_print_routes(_) -> + ?R:add_route("+/#"), + ?R:add_route("+/+"), + ?R:print_routes("a/b"). + t_has_routes(_) -> ?R:add_route(<<"devices/+/messages">>, node()), ?assert(?R:has_routes(<<"devices/+/messages">>)), ?R:delete_route(<<"devices/+/messages">>). +t_unexpected(_) -> + Router = emqx_misc:proc_name(?R, 1), + ?assertEqual(ignored, gen_server:call(Router, bad_request)), + ?assertEqual(ok, gen_server:cast(Router, bad_message)), + Router ! bad_info. + clear_tables() -> lists:foreach(fun mnesia:clear_table/1, [emqx_route, emqx_trie, emqx_trie_node]). diff --git a/test/emqx_trie_SUITE.erl b/test/emqx_trie_SUITE.erl index 043ce95f1..f10d13d0a 100644 --- a/test/emqx_trie_SUITE.erl +++ b/test/emqx_trie_SUITE.erl @@ -116,10 +116,11 @@ t_delete2(_) -> Fun = fun() -> ?TRIE:insert(<<"sensor">>), ?TRIE:insert(<<"sensor/1/metric/2">>), - ?TRIE:insert(<<"sensor/1/metric/3">>), + ?TRIE:insert(<<"sensor/+/metric/3">>), ?TRIE:delete(<<"sensor">>), ?TRIE:delete(<<"sensor/1/metric/2">>), - ?TRIE:delete(<<"sensor/1/metric/3">>), + ?TRIE:delete(<<"sensor/+/metric/3">>), + ?TRIE:delete(<<"sensor/+/metric/3">>), {?TRIE:lookup(<<"sensor">>), ?TRIE:lookup(<<"sensor/1">>)} end, ?assertEqual({atomic, {[], []}}, trans(Fun)). From 36a4eaec02bd53e7c9a89c1bec105fbde0aecaea Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 9 Jan 2019 12:10:54 +0800 Subject: [PATCH 21/41] Fix the test case for 'emqx_router:print_routes/1' --- test/emqx_router_SUITE.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/emqx_router_SUITE.erl b/test/emqx_router_SUITE.erl index 439866899..9174c258a 100644 --- a/test/emqx_router_SUITE.erl +++ b/test/emqx_router_SUITE.erl @@ -89,9 +89,9 @@ t_match_routes(_) -> ?assertEqual([], lists:sort(?R:match_routes(<<"a/b/c">>))). t_print_routes(_) -> - ?R:add_route("+/#"), - ?R:add_route("+/+"), - ?R:print_routes("a/b"). + ?R:add_route(<<"+/#">>), + ?R:add_route(<<"+/+">>), + ?R:print_routes(<<"a/b">>). t_has_routes(_) -> ?R:add_route(<<"devices/+/messages">>, node()), From de65e21270f79376f98e56f178746a1f3f0f26d4 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Tue, 8 Jan 2019 17:04:13 +0800 Subject: [PATCH 22/41] Disable lager This PR removes lager handlers and the `crash.log`, in case of some dependencies or plugins of emqx use lager. --- priv/emqx.schema | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/priv/emqx.schema b/priv/emqx.schema index d568e6c66..1f8e4fa6b 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -424,6 +424,17 @@ end}. hidden ]}. +%% disable lager +{mapping, "lager.handlers", "lager.handlers", [ + {default, []}, + hidden +]}. +{mapping, "lager.crash_log", "lager.crash_log", [ + {default, off}, + {datatype, flag}, + hidden +]}. + {translation, "emqx.primary_log_level", fun(Conf) -> cuttlefish:conf_get("log.level", Conf) end}. From 73ae8ad57f3e134777987bf2d64a0b0172439c09 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Thu, 10 Jan 2019 00:11:57 +0800 Subject: [PATCH 23/41] Update erlang/otp version for ci (#2141) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8960ab7c7..f2c483cc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: erlang otp_release: - - 21.0.4 + - 21.2 before_install: - git clone https://github.com/erlang/rebar3.git; cd rebar3; ./bootstrap; sudo mv rebar3 /usr/local/bin/; cd .. From fe0f5333b37c07d5c853b57aba6b65f5d4296d4e Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 9 Jan 2019 17:27:30 +0800 Subject: [PATCH 24/41] Improve the pool design - Move 'emqx_pool:start_link/0' to emqx_pool_sup module - Use the new log macros - Add more test cases --- src/emqx_kernel_sup.erl | 3 ++- src/emqx_pool.erl | 19 ++++++++------- src/emqx_pool_sup.erl | 9 ++++++- test/emqx_pool_SUITE.erl | 51 +++++++++++++++++++++++++--------------- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/emqx_kernel_sup.erl b/src/emqx_kernel_sup.erl index 40ec7cfd7..6289437ac 100644 --- a/src/emqx_kernel_sup.erl +++ b/src/emqx_kernel_sup.erl @@ -25,7 +25,7 @@ start_link() -> init([]) -> {ok, {{one_for_one, 10, 100}, - [child_spec(emqx_pool, supervisor), + [child_spec(emqx_pool_sup, supervisor), child_spec(emqx_alarm_mgr, worker), child_spec(emqx_hooks, worker), child_spec(emqx_stats, worker), @@ -41,6 +41,7 @@ child_spec(M, worker) -> shutdown => 5000, type => worker, modules => [M]}; + child_spec(M, supervisor) -> #{id => M, start => {M, start_link, []}, diff --git a/src/emqx_pool.erl b/src/emqx_pool.erl index 7b12bea69..e90cdda6b 100644 --- a/src/emqx_pool.erl +++ b/src/emqx_pool.erl @@ -16,9 +16,14 @@ -behaviour(gen_server). --export([start_link/0, start_link/2]). +-include("logger.hrl"). + +-export([start_link/2]). -export([submit/1, submit/2]). -export([async_submit/1, async_submit/2]). +-ifdef(TEST). +-export([worker/0]). +-endif. %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, @@ -28,10 +33,6 @@ -type(task() :: fun() | mfa() | {fun(), Args :: list(any())}). -%% @doc Start pooler supervisor. -start_link() -> - emqx_pool_sup:start_link(?POOL, random, {?MODULE, start_link, []}). - %% @doc Start pool. -spec(start_link(atom(), pos_integer()) -> emqx_types:startlink_ret()). start_link(Pool, Id) -> @@ -80,22 +81,22 @@ handle_call({submit, Task}, _From, State) -> {reply, catch run(Task), State}; handle_call(Req, _From, State) -> - emqx_logger:error("[Pool] unexpected call: ~p", [Req]), + ?ERROR("[Pool] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({async_submit, Task}, State) -> try run(Task) catch _:Error:Stacktrace -> - emqx_logger:error("[Pool] error: ~p, ~p", [Error, Stacktrace]) + ?ERROR("[Pool] error: ~p, ~p", [Error, Stacktrace]) end, {noreply, State}; handle_cast(Msg, State) -> - emqx_logger:error("[Pool] unexpected cast: ~p", [Msg]), + ?ERROR("[Pool] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_logger:error("[Pool] unexpected info: ~p", [Info]), + ?ERROR("[Pool] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #{pool := Pool, id := Id}) -> diff --git a/src/emqx_pool_sup.erl b/src/emqx_pool_sup.erl index eb81233f9..58a6a08cd 100644 --- a/src/emqx_pool_sup.erl +++ b/src/emqx_pool_sup.erl @@ -16,10 +16,13 @@ -behaviour(supervisor). --export([spec/1, spec/2, start_link/3, start_link/4]). +-export([spec/1, spec/2]). +-export([start_link/0, start_link/3, start_link/4]). -export([init/1]). +-define(POOL, emqx_pool). + -spec(spec(list()) -> supervisor:child_spec()). spec(Args) -> spec(pool_sup, Args). @@ -33,6 +36,10 @@ spec(ChildId, Args) -> type => supervisor, modules => [?MODULE]}. +%% @doc Start the default pool supervisor. +start_link() -> + start_link(?POOL, random, {?POOL, start_link, []}). + -spec(start_link(atom() | tuple(), atom(), mfa()) -> {ok, pid()} | {error, term()}). start_link(Pool, Type, MFA) -> start_link(Pool, Type, emqx_vm:schedulers(), MFA). diff --git a/test/emqx_pool_SUITE.erl b/test/emqx_pool_SUITE.erl index 3d7d0f7e5..36ad7c507 100644 --- a/test/emqx_pool_SUITE.erl +++ b/test/emqx_pool_SUITE.erl @@ -15,22 +15,22 @@ -module(emqx_pool_SUITE). -compile(export_all). - -compile(nowarn_export_all). -include("emqx_mqtt.hrl"). - -include_lib("eunit/include/eunit.hrl"). -all() -> [ - {group, submit_case}, - {group, async_submit_case} - ]. +all() -> + [ + {group, submit_case}, + {group, async_submit_case}, + t_unexpected + ]. groups() -> [ {submit_case, [sequence], [submit_mfa, submit_fa]}, - {async_submit_case, [sequence], [async_submit_mfa]} + {async_submit_case, [sequence], [async_submit_mfa, async_submit_ex]} ]. init_per_suite(Config) -> @@ -40,26 +40,39 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_testcase(_, Config) -> + {ok, Sup} = emqx_pool_sup:start_link(), + [{pool_sup, Sup}|Config]. + +end_per_testcase(_, Config) -> + Sup = proplists:get_value(pool_sup, Config), + exit(Sup, normal). + submit_mfa(_Config) -> - erlang:process_flag(trap_exit, true), - {ok, Pid} = emqx_pool:start_link(), Result = emqx_pool:submit({?MODULE, test_mfa, []}), - ?assertEqual(15, Result), - gen_server:stop(Pid, normal, 3000), - ok. + ?assertEqual(15, Result). submit_fa(_Config) -> - {ok, Pid} = emqx_pool:start_link(), Fun = fun(X) -> case X rem 2 of 0 -> {true, X div 2}; _ -> false end end, Result = emqx_pool:submit(Fun, [2]), - ?assertEqual({true, 1}, Result), - exit(Pid, normal). + ?assertEqual({true, 1}, Result). + +async_submit_mfa(_Config) -> + emqx_pool:async_submit({?MODULE, test_mfa, []}), + emqx_pool:async_submit(fun ?MODULE:test_mfa/0, []). + +async_submit_ex(_) -> + emqx_pool:async_submit(fun error_fun/0). + +t_unexpected(_) -> + Pid = emqx_pool:worker(), + ?assertEqual(ignored, gen_server:call(Pid, bad_request)), + ?assertEqual(ok, gen_server:cast(Pid, bad_msg)), + Pid ! bad_info, + ok = gen_server:stop(Pid). test_mfa() -> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]). -async_submit_mfa(_Config) -> - {ok, Pid} = emqx_pool:start_link(), - emqx_pool:async_submit({?MODULE, test_mfa, []}), - exit(Pid, normal). +error_fun() -> error(test_error). From e882af9369ab966a789f061533361ed59001c706 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 9 Jan 2019 19:07:34 +0800 Subject: [PATCH 25/41] Use the new logger macros --- src/emqx_alarm_mgr.erl | 11 ++++++----- src/emqx_banned.erl | 7 ++++--- src/emqx_broker.erl | 15 ++++++++------- src/emqx_broker_helper.erl | 8 +++++--- src/emqx_cm.erl | 7 ++++--- src/emqx_ctl.erl | 13 ++++++++----- src/emqx_hooks.erl | 8 +++++--- src/emqx_metrics.erl | 13 +++++++------ src/emqx_router.erl | 7 ++++--- src/emqx_router_helper.erl | 9 +++++---- src/emqx_session_sup.erl | 14 +++++++------- src/emqx_shared_sub.erl | 9 +++++---- src/emqx_sm.erl | 11 ++++++----- src/emqx_sm_registry.erl | 7 ++++--- src/emqx_stats.erl | 14 ++++++++------ src/emqx_sys.erl | 7 ++++--- src/emqx_sys_mon.erl | 27 +++++++++++++-------------- src/emqx_zone.erl | 7 ++++--- 18 files changed, 107 insertions(+), 87 deletions(-) diff --git a/src/emqx_alarm_mgr.erl b/src/emqx_alarm_mgr.erl index fd2a42aa7..6573904d6 100644 --- a/src/emqx_alarm_mgr.erl +++ b/src/emqx_alarm_mgr.erl @@ -17,6 +17,7 @@ -behaviour(gen_event). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0]). -export([alarm_fun/0, get_alarms/0, set_alarm/1, clear_alarm/1]). @@ -84,7 +85,7 @@ handle_event({set_alarm, Alarm = #alarm{id = AlarmId}}, State = #{alarms := Alar {ok, Json} -> emqx_broker:safe_publish(alarm_msg(alert, AlarmId, Json)); {error, Reason} -> - emqx_logger:error("[AlarmMgr] Failed to encode alarm: ~p", [Reason]) + ?ERROR("[AlarmMgr] Failed to encode alarm: ~p", [Reason]) end, {ok, State#{alarms := [Alarm|Alarms]}}; @@ -93,23 +94,23 @@ handle_event({clear_alarm, AlarmId}, State = #{alarms := Alarms}) -> {ok, Json} -> emqx_broker:safe_publish(alarm_msg(clear, AlarmId, Json)); {error, Reason} -> - emqx_logger:error("[AlarmMgr] Failed to encode clear: ~p", [Reason]) + ?ERROR("[AlarmMgr] Failed to encode clear: ~p", [Reason]) end, {ok, State#{alarms := lists:keydelete(AlarmId, 2, Alarms)}, hibernate}; handle_event(Event, State)-> - emqx_logger:error("[AlarmMgr] unexpected event: ~p", [Event]), + ?ERROR("[AlarmMgr] unexpected event: ~p", [Event]), {ok, State}. handle_info(Info, State) -> - emqx_logger:error("[AlarmMgr] unexpected info: ~p", [Info]), + ?ERROR("[AlarmMgr] unexpected info: ~p", [Info]), {ok, State}. handle_call(get_alarms, State = #{alarms := Alarms}) -> {ok, Alarms, State}; handle_call(Req, State) -> - emqx_logger:error("[AlarmMgr] unexpected call: ~p", [Req]), + ?ERROR("[AlarmMgr] unexpected call: ~p", [Req]), {ok, ignored, State}. terminate(swap, State) -> diff --git a/src/emqx_banned.erl b/src/emqx_banned.erl index 0c9ffdd10..9da05cd15 100644 --- a/src/emqx_banned.erl +++ b/src/emqx_banned.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). %% Mnesia bootstrap -export([mnesia/1]). @@ -77,11 +78,11 @@ init([]) -> {ok, ensure_expiry_timer(#{expiry_timer => undefined})}. handle_call(Req, _From, State) -> - emqx_logger:error("[Banned] unexpected call: ~p", [Req]), + ?ERROR("[Banned] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[Banned] unexpected msg: ~p", [Msg]), + ?ERROR("[Banned] unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) -> @@ -89,7 +90,7 @@ handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) -> {noreply, ensure_expiry_timer(State), hibernate}; handle_info(Info, State) -> - emqx_logger:error("[Banned] unexpected info: ~p", [Info]), + ?ERROR("[Banned] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #{expiry_timer := TRef}) -> diff --git a/src/emqx_broker.erl b/src/emqx_broker.erl index 429c6097d..a8da25ce6 100644 --- a/src/emqx_broker.erl +++ b/src/emqx_broker.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/2]). -export([subscribe/1, subscribe/2, subscribe/3]). @@ -170,7 +171,7 @@ publish(Msg) when is_record(Msg, message) -> Delivery = route(aggre(emqx_router:match_routes(Topic)), delivery(Msg1)), Delivery#delivery.results; {stop, _} -> - emqx_logger:warning("Stop publishing: ~s", [emqx_message:format(Msg)]), + ?WARN("Stop publishing: ~s", [emqx_message:format(Msg)]), [] end. @@ -181,7 +182,7 @@ safe_publish(Msg) when is_record(Msg, message) -> publish(Msg) catch _:Error:Stacktrace -> - emqx_logger:error("[Broker] publish error: ~p~n~p~n~p", [Error, Msg, Stacktrace]) + ?ERROR("[Broker] publish error: ~p~n~p~n~p", [Error, Msg, Stacktrace]) after ok end. @@ -228,7 +229,7 @@ forward(Node, To, Delivery) -> %% rpc:call to ensure the delivery, but the latency:( case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of {badrpc, Reason} -> - emqx_logger:error("[Broker] Failed to forward msg to ~s: ~p", [Node, Reason]), + ?ERROR("[Broker] Failed to forward msg to ~s: ~p", [Node, Reason]), Delivery; Delivery1 -> Delivery1 end. @@ -396,14 +397,14 @@ handle_call({subscribe, Topic, I}, _From, State) -> {reply, Ok, State}; handle_call(Req, _From, State) -> - emqx_logger:error("[Broker] unexpected call: ~p", [Req]), + ?ERROR("[Broker] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({subscribe, Topic}, State) -> case emqx_router:do_add_route(Topic) of ok -> ok; {error, Reason} -> - emqx_logger:error("[Broker] Failed to add route: ~p", [Reason]) + ?ERROR("[Broker] Failed to add route: ~p", [Reason]) end, {noreply, State}; @@ -426,11 +427,11 @@ handle_cast({unsubscribed, Topic, I}, State) -> {noreply, State}; handle_cast(Msg, State) -> - emqx_logger:error("[Broker] unexpected cast: ~p", [Msg]), + ?ERROR("[Broker] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_logger:error("[Broker] unexpected info: ~p", [Info]), + ?ERROR("[Broker] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #{pool := Pool, id := Id}) -> diff --git a/src/emqx_broker_helper.erl b/src/emqx_broker_helper.erl index 1ea3f3668..d7eb6bfa2 100644 --- a/src/emqx_broker_helper.erl +++ b/src/emqx_broker_helper.erl @@ -16,6 +16,8 @@ -behaviour(gen_server). +-include("logger.hrl"). + -export([start_link/0]). -export([register_sub/2]). -export([lookup_subid/1, lookup_subpid/1]). @@ -96,7 +98,7 @@ init([]) -> {ok, #{pmon => emqx_pmon:new()}}. handle_call(Req, _From, State) -> - emqx_logger:error("[BrokerHelper] unexpected call: ~p", [Req]), + ?ERROR("[BrokerHelper] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({register_sub, SubPid, SubId}, State = #{pmon := PMon}) -> @@ -105,7 +107,7 @@ handle_cast({register_sub, SubPid, SubId}, State = #{pmon := PMon}) -> {noreply, State#{pmon := emqx_pmon:monitor(SubPid, PMon)}}; handle_cast(Msg, State) -> - emqx_logger:error("[BrokerHelper] unexpected cast: ~p", [Msg]), + ?ERROR("[BrokerHelper] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #{pmon := PMon}) -> @@ -116,7 +118,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #{pmon := PMon}) {noreply, State#{pmon := PMon1}}; handle_info(Info, State) -> - emqx_logger:error("[BrokerHelper] unexpected info: ~p", [Info]), + ?ERROR("[BrokerHelper] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl index b8a57bc50..1d5ae0c58 100644 --- a/src/emqx_cm.erl +++ b/src/emqx_cm.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0]). @@ -140,7 +141,7 @@ init([]) -> {ok, #{conn_pmon => emqx_pmon:new()}}. handle_call(Req, _From, State) -> - emqx_logger:error("[CM] unexpected call: ~p", [Req]), + ?ERROR("[CM] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({notify, {registered, ClientId, ConnPid}}, State = #{conn_pmon := PMon}) -> @@ -150,7 +151,7 @@ handle_cast({notify, {unregistered, ConnPid}}, State = #{conn_pmon := PMon}) -> {noreply, State#{conn_pmon := emqx_pmon:demonitor(ConnPid, PMon)}}; handle_cast(Msg, State) -> - emqx_logger:error("[CM] unexpected cast: ~p", [Msg]), + ?ERROR("[CM] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{conn_pmon := PMon}) -> @@ -161,7 +162,7 @@ handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{conn_pmon := PMon} {noreply, State#{conn_pmon := PMon1}}; handle_info(Info, State) -> - emqx_logger:error("[CM] unexpected info: ~p", [Info]), + ?ERROR("[CM] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_ctl.erl b/src/emqx_ctl.erl index 1d2fb13a3..13cfab945 100644 --- a/src/emqx_ctl.erl +++ b/src/emqx_ctl.erl @@ -16,6 +16,8 @@ -behaviour(gen_server). +-include("logger.hrl"). + -export([start_link/0]). -export([register_command/2, register_command/3, unregister_command/1]). -export([run_command/1, run_command/2, lookup_command/1]). @@ -63,7 +65,7 @@ run_command(Cmd, Args) when is_atom(Cmd) -> _ -> ok catch _:Reason:Stacktrace -> - emqx_logger:error("[CTL] CMD Error:~p, Stacktrace:~p", [Reason, Stacktrace]), + ?ERROR("[Ctl] CMD Error:~p, Stacktrace:~p", [Reason, Stacktrace]), {error, Reason} end; [] -> @@ -91,14 +93,14 @@ init([]) -> {ok, #state{seq = 0}}. handle_call(Req, _From, State) -> - emqx_logger:error("unexpected call: ~p", [Req]), + ?ERROR("[Ctl] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({register_command, Cmd, MF, Opts}, State = #state{seq = Seq}) -> case ets:match(?TAB, {{'$1', Cmd}, '_', '_'}) of [] -> ets:insert(?TAB, {{Seq, Cmd}, MF, Opts}); [[OriginSeq] | _] -> - emqx_logger:warning("[CTL] cmd ~s is overidden by ~p", [Cmd, MF]), + ?WARN("[Ctl] cmd ~s is overidden by ~p", [Cmd, MF]), ets:insert(?TAB, {{OriginSeq, Cmd}, MF, Opts}) end, noreply(next_seq(State)); @@ -108,11 +110,11 @@ handle_cast({unregister_command, Cmd}, State) -> noreply(State); handle_cast(Msg, State) -> - emqx_logger:error("Unexpected cast: ~p", [Msg]), + ?ERROR("[Ctl] unexpected cast: ~p", [Msg]), noreply(State). handle_info(Info, State) -> - emqx_logger:error("unexpected info: ~p", [Info]), + ?ERROR("[Ctl] unexpected info: ~p", [Info]), noreply(State). terminate(_Reason, _State) -> @@ -151,3 +153,4 @@ register_command_test_() -> }. -endif. + diff --git a/src/emqx_hooks.erl b/src/emqx_hooks.erl index 55b2476ee..a2e08a439 100644 --- a/src/emqx_hooks.erl +++ b/src/emqx_hooks.erl @@ -16,6 +16,8 @@ -behaviour(gen_server). +-include("logger.hrl"). + -export([start_link/0, stop/0]). %% Hooks API @@ -152,7 +154,7 @@ handle_call({add, HookPoint, Callback = #callback{action = Action}}, _From, Stat {reply, Reply, State}; handle_call(Req, _From, State) -> - emqx_logger:error("[Hooks] unexpected call: ~p", [Req]), + ?ERROR("[Hooks] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({del, HookPoint, Action}, State) -> @@ -165,11 +167,11 @@ handle_cast({del, HookPoint, Action}, State) -> {noreply, State}; handle_cast(Msg, State) -> - emqx_logger:error("[Hooks] unexpected msg: ~p", [Msg]), + ?ERROR("[Hooks] unexpected msg: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_logger:error("[Hooks] unexpected info: ~p", [Info]), + ?ERROR("[Hooks] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_metrics.erl b/src/emqx_metrics.erl index b4b3a1307..5453f6ad3 100644 --- a/src/emqx_metrics.erl +++ b/src/emqx_metrics.erl @@ -14,6 +14,7 @@ -module(emqx_metrics). +-include("logger.hrl"). -include("emqx_mqtt.hrl"). -export([start_link/0]). @@ -182,7 +183,7 @@ commit() -> case get('$metrics') of undefined -> ok; Metrics -> - maps:fold(fun({Type, Metric}, Val, _Acc) -> + maps:fold(fun({Type, Metric}, Val, _Acc) -> update_counter(key(Type, Metric), {2, Val}) end, 0, Metrics), erase('$metrics') @@ -279,9 +280,9 @@ qos_sent(?QOS_1) -> qos_sent(?QOS_2) -> inc('messages/qos2/sent'). -%%----------------------------------------------------------------------------- +%%------------------------------------------------------------------------------ %% gen_server callbacks -%%----------------------------------------------------------------------------- +%%------------------------------------------------------------------------------ init([]) -> % Create metrics table @@ -290,15 +291,15 @@ init([]) -> {ok, #{}, hibernate}. handle_call(Req, _From, State) -> - emqx_logger:error("[Metrics] unexpected call: ~p", [Req]), + ?ERROR("[Metrics] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[Metrics] unexpected cast: ~p", [Msg]), + ?ERROR("[Metrics] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_logger:error("[Metrics] unexpected info: ~p", [Info]), + ?ERROR("[Metrics] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #{}) -> diff --git a/src/emqx_router.erl b/src/emqx_router.erl index f01db0d52..6989d90a6 100644 --- a/src/emqx_router.erl +++ b/src/emqx_router.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -include_lib("ekka/include/ekka.hrl"). %% Mnesia bootstrap @@ -177,15 +178,15 @@ handle_call({delete_route, Topic, Dest}, _From, State) -> {reply, Ok, State}; handle_call(Req, _From, State) -> - emqx_logger:error("[Router] unexpected call: ~p", [Req]), + ?ERROR("[Router] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[Router] unexpected cast: ~p", [Msg]), + ?ERROR("[Router] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_logger:error("[Router] unexpected info: ~p", [Info]), + ?ERROR("[Router] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #{pool := Pool, id := Id}) -> diff --git a/src/emqx_router_helper.erl b/src/emqx_router_helper.erl index c32800a24..9eaa501d2 100644 --- a/src/emqx_router_helper.erl +++ b/src/emqx_router_helper.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). %% Mnesia bootstrap -export([mnesia/1]). @@ -94,11 +95,11 @@ init([]) -> {ok, #{nodes => Nodes}, hibernate}. handle_call(Req, _From, State) -> - emqx_logger:error("[RouterHelper] unexpected call: ~p", [Req]), + ?ERROR("[RouterHelper] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[RouterHelper] unexpected cast: ~p", [Msg]), + ?ERROR("[RouterHelper] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({mnesia_table_event, {write, {?ROUTING_NODE, Node, _}, _}}, State = #{nodes := Nodes}) -> @@ -114,7 +115,7 @@ handle_info({mnesia_table_event, {delete, {?ROUTING_NODE, _Node}, _}}, State) -> {noreply, State}; handle_info({mnesia_table_event, Event}, State) -> - emqx_logger:error("[RouterHelper] unexpected mnesia_table_event: ~p", [Event]), + ?ERROR("[RouterHelper] unexpected mnesia_table_event: ~p", [Event]), {noreply, State}; handle_info({nodedown, Node}, State = #{nodes := Nodes}) -> @@ -132,7 +133,7 @@ handle_info({membership, _Event}, State) -> {noreply, State}; handle_info(Info, State) -> - emqx_logger:error("[RouteHelper] unexpected info: ~p", [Info]), + ?ERROR("[RouteHelper] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_session_sup.erl b/src/emqx_session_sup.erl index efa64815b..aa430ced9 100644 --- a/src/emqx_session_sup.erl +++ b/src/emqx_session_sup.erl @@ -16,6 +16,8 @@ -behaviour(gen_server). +-include("logger.hrl"). + -export([start_link/1]). -export([start_session/1, count_sessions/0]). @@ -34,8 +36,6 @@ -define(SUP, ?MODULE). -define(BATCH_EXIT, 100000). --define(ERROR_MSG(Format, Args), - error_logger:error_msg("[~s] " ++ Format, [?MODULE | Args])). %% @doc Start session supervisor. -spec(start_link(map()) -> emqx_types:startlink_ret()). @@ -83,8 +83,8 @@ handle_call({start_session, SessAttrs = #{client_id := ClientId}}, _From, reply({error, Reason}, State) catch _:Error:Stk -> - ?ERROR_MSG("Failed to start session ~p: ~p, stacktrace:~n~p", - [ClientId, Error, Stk]), + ?ERROR("Failed to start session ~p: ~p, stacktrace:~n~p", + [ClientId, Error, Stk]), reply({error, Error}, State) end; @@ -92,11 +92,11 @@ handle_call(count_sessions, _From, State = #state{sessions = SessMap}) -> {reply, maps:size(SessMap), State}; handle_call(Req, _From, State) -> - ?ERROR_MSG("unexpected call: ~p", [Req]), + ?ERROR("unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - ?ERROR_MSG("unexpected cast: ~p", [Msg]), + ?ERROR("unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({'EXIT', Pid, _Reason}, State = #state{sessions = SessMap, clean_down = CleanDown}) -> @@ -108,7 +108,7 @@ handle_info({'EXIT', Pid, _Reason}, State = #state{sessions = SessMap, clean_dow {noreply, State#state{sessions = SessMap1}}; handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR("unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, State) -> diff --git a/src/emqx_shared_sub.erl b/src/emqx_shared_sub.erl index b7e41213b..67f03a93c 100644 --- a/src/emqx_shared_sub.erl +++ b/src/emqx_shared_sub.erl @@ -18,6 +18,7 @@ -include("emqx.hrl"). -include("emqx_mqtt.hrl"). +-include("logger.hrl"). %% Mnesia bootstrap -export([mnesia/1]). @@ -284,11 +285,11 @@ handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) -> {reply, ok, State}; handle_call(Req, _From, State) -> - emqx_logger:error("[SharedSub] unexpected call: ~p", [Req]), + ?ERROR("[SharedSub] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[SharedSub] unexpected cast: ~p", [Msg]), + ?ERROR("[SharedSub] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) -> @@ -303,12 +304,12 @@ handle_info({mnesia_table_event, _Event}, State) -> {noreply, State}; handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMon}) -> - emqx_logger:info("[SharedSub] shared subscriber down: ~p", [SubPid]), + ?INFO("[SharedSub] shared subscriber down: ~p", [SubPid]), cleanup_down(SubPid), {noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})}; handle_info(Info, State) -> - emqx_logger:error("[SharedSub] unexpected info: ~p", [Info]), + ?ERROR("[SharedSub] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_sm.erl b/src/emqx_sm.erl index ab270f1af..dcbe9028e 100644 --- a/src/emqx_sm.erl +++ b/src/emqx_sm.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0]). @@ -90,7 +91,7 @@ discard_session(ClientId, ConnPid) when is_binary(ClientId) -> try emqx_session:discard(SessPid, ConnPid) catch _:Error:_Stk -> - emqx_logger:error("[SM] Failed to discard ~p: ~p", [SessPid, Error]) + ?ERROR("[SM] Failed to discard ~p: ~p", [SessPid, Error]) end end, lookup_session_pids(ClientId)). @@ -104,7 +105,7 @@ resume_session(ClientId, SessAttrs = #{conn_pid := ConnPid}) -> {ok, SessPid}; SessPids -> [SessPid|StalePids] = lists:reverse(SessPids), - emqx_logger:error("[SM] More than one session found: ~p", [SessPids]), + ?ERROR("[SM] More than one session found: ~p", [SessPids]), lists:foreach(fun(StalePid) -> catch emqx_session:discard(StalePid, ConnPid) end, StalePids), @@ -226,15 +227,15 @@ init([]) -> {ok, #{}}. handle_call(Req, _From, State) -> - emqx_logger:error("[SM] unexpected call: ~p", [Req]), + ?ERROR("[SM] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[SM] unexpected cast: ~p", [Msg]), + ?ERROR("[SM] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_logger:error("[SM] unexpected info: ~p", [Info]), + ?ERROR("[SM] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_sm_registry.erl b/src/emqx_sm_registry.erl index a7f44d771..327be77c1 100644 --- a/src/emqx_sm_registry.erl +++ b/src/emqx_sm_registry.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0]). -export([is_enabled/0]). @@ -81,11 +82,11 @@ init([]) -> {ok, #{}}. handle_call(Req, _From, State) -> - emqx_logger:error("[Registry] unexpected call: ~p", [Req]), + ?ERROR("[Registry] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[Registry] unexpected cast: ~p", [Msg]), + ?ERROR("[Registry] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({membership, {mnesia, down, Node}}, State) -> @@ -99,7 +100,7 @@ handle_info({membership, _Event}, State) -> {noreply, State}; handle_info(Info, State) -> - emqx_logger:error("[Registry] unexpected info: ~p", [Info]), + ?ERROR("[Registry] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_stats.erl b/src/emqx_stats.erl index 790c397b9..d86886c28 100644 --- a/src/emqx_stats.erl +++ b/src/emqx_stats.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0, start_link/1, stop/0]). @@ -164,7 +165,7 @@ start_timer(#state{tick_ms = Ms} = State) -> handle_call(stop, _From, State) -> {stop, normal, _Reply = ok, State}; handle_call(Req, _From, State) -> - emqx_logger:error("[Stats] unexpected call: ~p", [Req]), + ?ERROR("[Stats] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({setstat, Stat, MaxStat, Val}, State) -> @@ -182,7 +183,7 @@ handle_cast({setstat, Stat, MaxStat, Val}, State) -> handle_cast({update_interval, Update = #update{name = Name}}, State = #state{updates = Updates}) -> case lists:keyfind(Name, #update.name, Updates) of #update{} -> - emqx_logger:error("[Stats]: duplicated update: ~s", [Name]), + ?ERROR("[Stats]: duplicated update: ~s", [Name]), {noreply, State}; false -> {noreply, State#state{updates = [Update | Updates]}} @@ -192,7 +193,7 @@ handle_cast({cancel_update, Name}, State = #state{updates = Updates}) -> {noreply, State#state{updates = lists:keydelete(Name, #update.name, Updates)}}; handle_cast(Msg, State) -> - emqx_logger:error("[Stats] unexpected cast: ~p", [Msg]), + ?ERROR("[Stats] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({timeout, TRef, tick}, State = #state{timer = TRef, updates = Updates}) -> @@ -200,8 +201,9 @@ handle_info({timeout, TRef, tick}, State = #state{timer = TRef, updates = Update fun(Update = #update{name = Name, countdown = C, interval = I, func = UpFun}, Acc) when C =< 0 -> try UpFun() - catch _:Error -> - emqx_logger:error("[Stats] update ~s error: ~p", [Name, Error]) + catch + _:Error -> + ?ERROR("[Stats] update ~s error: ~p", [Name, Error]) end, [Update#update{countdown = I} | Acc]; (Update = #update{countdown = C}, Acc) -> @@ -210,7 +212,7 @@ handle_info({timeout, TRef, tick}, State = #state{timer = TRef, updates = Update {noreply, start_timer(State#state{updates = Updates1}), hibernate}; handle_info(Info, State) -> - emqx_logger:error("[Stats] unexpected info: ~p", [Info]), + ?ERROR("[Stats] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{timer = TRef}) -> diff --git a/src/emqx_sys.erl b/src/emqx_sys.erl index 5001f733b..d2fc3f546 100644 --- a/src/emqx_sys.erl +++ b/src/emqx_sys.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0]). -export([version/0, uptime/0, datetime/0, sysdescr/0, sys_interval/0]). @@ -98,11 +99,11 @@ handle_call(uptime, _From, State) -> {reply, uptime(State), State}; handle_call(Req, _From, State) -> - emqx_logger:error("[SYS] unexpected call: ~p", [Req]), + ?ERROR("[SYS] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[SYS] unexpected cast: ~p", [Msg]), + ?ERROR("[SYS] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({timeout, TRef, heartbeat}, State = #state{heartbeat = TRef}) -> @@ -119,7 +120,7 @@ handle_info({timeout, TRef, tick}, State = #state{ticker = TRef, version = Versi {noreply, tick(State), hibernate}; handle_info(Info, State) -> - emqx_logger:error("[SYS] unexpected info: ~p", [Info]), + ?ERROR("[SYS] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{heartbeat = TRef1, ticker = TRef2}) -> diff --git a/src/emqx_sys_mon.erl b/src/emqx_sys_mon.erl index e3bd4a2a9..d5c86e9b6 100644 --- a/src/emqx_sys_mon.erl +++ b/src/emqx_sys_mon.erl @@ -16,6 +16,8 @@ -behavior(gen_server). +-include("logger.hrl"). + -export([start_link/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, @@ -24,13 +26,9 @@ -record(state, {timer, events}). -define(SYSMON, ?MODULE). --define(LOG(Msg, ProcInfo), - emqx_logger:warning(#{sysmon => true}, "[SYSMON] ~s~n~p", [WarnMsg, ProcInfo])). --define(LOG(Msg, ProcInfo, PortInfo), - emqx_logger:warning(#{sysmon => true}, "[SYSMON] ~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])). %% @doc Start system monitor --spec(start_link(Opts :: list(tuple())) -> {ok, pid()} | ignore | {error, term()}). +-spec(start_link(Opts :: list(tuple())) -> emqx_types:startlink_ret()). start_link(Opts) -> gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []). @@ -40,6 +38,7 @@ start_link(Opts) -> init([Opts]) -> erlang:system_monitor(self(), parse_opt(Opts)), + emqx_logger:set_proc_metadata(#{sysmon => true}), {ok, start_timer(#state{events = []})}. start_timer(State) -> @@ -71,18 +70,18 @@ parse_opt([_Opt|Opts], Acc) -> parse_opt(Opts, Acc). handle_call(Req, _From, State) -> - emqx_logger:error("[SYSMON] unexpected call: ~p", [Req]), + ?ERROR("[SYSMON] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast(Msg, State) -> - emqx_logger:error("[SYSMON] unexpected cast: ~p", [Msg]), + ?ERROR("[SYSMON] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({monitor, Pid, long_gc, Info}, State) -> suppress({long_gc, Pid}, fun() -> WarnMsg = io_lib:format("long_gc warning: pid = ~p, info: ~p", [Pid, Info]), - ?LOG(WarnMsg, procinfo(Pid)), + ?WARN("[SYSMON] ~s~n~p", [WarnMsg, procinfo(Pid)]), safe_publish(long_gc, WarnMsg) end, State); @@ -90,7 +89,7 @@ handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) -> suppress({long_schedule, Pid}, fun() -> WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]), - ?LOG(WarnMsg, procinfo(Pid)), + ?WARN("[SYSMON] ~s~n~p", [WarnMsg, procinfo(Pid)]), safe_publish(long_schedule, WarnMsg) end, State); @@ -98,7 +97,7 @@ handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) -> suppress({long_schedule, Port}, fun() -> WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]), - ?LOG(WarnMsg, erlang:port_info(Port)), + ?WARN("[SYSMON] ~s~n~p", [WarnMsg, erlang:port_info(Port)]), safe_publish(long_schedule, WarnMsg) end, State); @@ -106,7 +105,7 @@ handle_info({monitor, Pid, large_heap, Info}, State) -> suppress({large_heap, Pid}, fun() -> WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]), - ?LOG(WarnMsg, procinfo(Pid)), + ?WARN("[SYSMON] ~s~n~p", [WarnMsg, procinfo(Pid)]), safe_publish(large_heap, WarnMsg) end, State); @@ -114,7 +113,7 @@ handle_info({monitor, SusPid, busy_port, Port}, State) -> suppress({busy_port, Port}, fun() -> WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]), - ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)), + ?WARN("[SYSMON] ~s~n~p~n~p", [WarnMsg, procinfo(SusPid), erlang:port_info(Port)]), safe_publish(busy_port, WarnMsg) end, State); @@ -122,7 +121,7 @@ handle_info({monitor, SusPid, busy_dist_port, Port}, State) -> suppress({busy_dist_port, Port}, fun() -> WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]), - ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)), + ?WARN("[SYSMON] ~s~n~p~n~p", [WarnMsg, procinfo(SusPid), erlang:port_info(Port)]), safe_publish(busy_dist_port, WarnMsg) end, State); @@ -130,7 +129,7 @@ handle_info({timeout, _Ref, reset}, State) -> {noreply, State#state{events = []}, hibernate}; handle_info(Info, State) -> - logger:error("[SYSMON] unexpected Info: ~p", [Info]), + ?ERROR("[SYSMON] unexpected Info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{timer = TRef}) -> diff --git a/src/emqx_zone.erl b/src/emqx_zone.erl index d119abe52..890d14a89 100644 --- a/src/emqx_zone.erl +++ b/src/emqx_zone.erl @@ -17,6 +17,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("logger.hrl"). -export([start_link/0]). -export([get_env/2, get_env/3]). @@ -76,7 +77,7 @@ handle_call(force_reload, _From, State) -> {reply, ok, State}; handle_call(Req, _From, State) -> - emqx_logger:error("[Zone] unexpected call: ~p", [Req]), + ?ERROR("[Zone] unexpected call: ~p", [Req]), {reply, ignored, State}. handle_cast({set_env, Zone, Key, Val}, State) -> @@ -84,7 +85,7 @@ handle_cast({set_env, Zone, Key, Val}, State) -> {noreply, State}; handle_cast(Msg, State) -> - emqx_logger:error("[Zone] unexpected cast: ~p", [Msg]), + ?ERROR("[Zone] unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(reload, State) -> @@ -92,7 +93,7 @@ handle_info(reload, State) -> {noreply, ensure_reload_timer(State#{timer := undefined}), hibernate}; handle_info(Info, State) -> - emqx_logger:error("[Zone] unexpected info: ~p", [Info]), + ?ERROR("[Zone] unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> From 30f32de13a24405bdb305b14fba24ced457b4e76 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 9 Jan 2019 19:41:38 +0800 Subject: [PATCH 26/41] Use map to replace 'state' record --- src/emqx_sys_mon.erl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/emqx_sys_mon.erl b/src/emqx_sys_mon.erl index d5c86e9b6..0a36cae65 100644 --- a/src/emqx_sys_mon.erl +++ b/src/emqx_sys_mon.erl @@ -19,11 +19,10 @@ -include("logger.hrl"). -export([start_link/1]). - -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - --record(state, {timer, events}). +%% compress unused warning +-export([procinfo/1]). -define(SYSMON, ?MODULE). @@ -32,17 +31,17 @@ start_link(Opts) -> gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []). -%%----------------------------------------------------------------------------- +%%------------------------------------------------------------------------------ %% gen_server callbacks -%%----------------------------------------------------------------------------- +%%------------------------------------------------------------------------------ init([Opts]) -> erlang:system_monitor(self(), parse_opt(Opts)), emqx_logger:set_proc_metadata(#{sysmon => true}), - {ok, start_timer(#state{events = []})}. + {ok, start_timer(#{timer => undefined, events => []})}. start_timer(State) -> - State#state{timer = emqx_misc:start_timer(timer:seconds(2), reset)}. + State#{timer := emqx_misc:start_timer(timer:seconds(2), reset)}. parse_opt(Opts) -> parse_opt(Opts, []). @@ -126,23 +125,23 @@ handle_info({monitor, SusPid, busy_dist_port, Port}, State) -> end, State); handle_info({timeout, _Ref, reset}, State) -> - {noreply, State#state{events = []}, hibernate}; + {noreply, State#{events := []}, hibernate}; handle_info(Info, State) -> ?ERROR("[SYSMON] unexpected Info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{timer = TRef}) -> +terminate(_Reason, #{timer := TRef}) -> emqx_misc:cancel_timer(TRef). code_change(_OldVsn, State, _Extra) -> {ok, State}. -suppress(Key, SuccFun, State = #state{events = Events}) -> +suppress(Key, SuccFun, State = #{events := Events}) -> case lists:member(Key, Events) of true -> {noreply, State}; false -> SuccFun(), - {noreply, State#state{events = [Key|Events]}} + {noreply, State#{events := [Key|Events]}} end. procinfo(Pid) -> From 3e15ac0bbbe5f8f6ab62a4facba85b5bf76834cd Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 9 Jan 2019 15:52:19 +0800 Subject: [PATCH 27/41] Improve the design of 'emqx_client' module Use '{next_event, cast, Packet}' to replace 'gen_statem:cast/2' --- src/emqx_client.erl | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/emqx_client.erl b/src/emqx_client.erl index 015bcc51b..4c19bbcc1 100644 --- a/src/emqx_client.erl +++ b/src/emqx_client.erl @@ -39,10 +39,10 @@ -export([initialized/3, waiting_for_connack/3, connected/3]). -export([init/1, callback_mode/0, handle_event/4, terminate/3, code_change/4]). --export_type([client/0, properties/0, payload/0, - pubopt/0, subopt/0, request_input/0, - response_payload/0, request_handler/0, +-export_type([client/0, properties/0, payload/0, pubopt/0, subopt/0, + request_input/0, response_payload/0, request_handler/0, corr_data/0]). + -export_type([host/0, option/0]). %% Default timeout @@ -697,7 +697,8 @@ waiting_for_connack(cast, ?CONNACK_PACKET(?RC_SUCCESS, waiting_for_connack(cast, ?CONNACK_PACKET(ReasonCode, _SessPresent, - Properties), State = #state{ proto_ver = ProtoVer}) -> + Properties), + State = #state{proto_ver = ProtoVer}) -> Reason = emqx_reason_codes:name(ReasonCode, ProtoVer), case take_call(connect, State) of {value, #call{from = From}, _State} -> @@ -999,7 +1000,7 @@ should_ping(Sock) -> handle_event(info, {TcpOrSsL, _Sock, Data}, _StateName, State) when TcpOrSsL =:= tcp; TcpOrSsL =:= ssl -> emqx_logger:debug("RECV Data: ~p", [Data]), - receive_loop(Data, run_sock(State)); + process_incoming(Data, [], run_sock(State)); handle_event(info, {Error, _Sock, Reason}, _StateName, State) when Error =:= tcp_error; Error =:= ssl_error -> @@ -1323,21 +1324,25 @@ run_sock(State = #state{socket = Sock}) -> emqx_client_sock:setopts(Sock, [{active, once}]), State. %%------------------------------------------------------------------------------ -%% Receive Loop +%% Process incomming -receive_loop(<<>>, State) -> - {keep_state, State}; +process_incoming(<<>>, [Packet], State) -> + {keep_state, State, {next_event, cast, Packet}}; -receive_loop(Bytes, State = #state{parse_state = ParseState}) -> - case catch emqx_frame:parse(Bytes, ParseState) of +process_incoming(<<>>, Packets, State) -> + NextEvts = [{next_event, cast, Packet} || Packet <- lists:reverse(Packets)], + {keep_state, State, NextEvts}; + +process_incoming(Bytes, Packets, State = #state{parse_state = ParseState}) -> + try emqx_frame:parse(Bytes, ParseState) of {ok, Packet, Rest} -> - ok = gen_statem:cast(self(), Packet), - receive_loop(Rest, init_parse_state(State)); + process_incoming(Rest, [Packet|Packets], init_parse_state(State)); {more, NewParseState} -> {keep_state, State#state{parse_state = NewParseState}}; {error, Reason} -> - {stop, Reason}; - {'EXIT', Error} -> + {stop, Reason} + catch + error:Error -> {stop, Error} end. From 5ccaaed34ce5853bd0d217f9817cfc9e95404204 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 10 Jan 2019 09:17:21 +0800 Subject: [PATCH 28/41] Add 'next_events/1' and fix the 'process_incoming/3' function --- src/emqx_client.erl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/emqx_client.erl b/src/emqx_client.erl index 4c19bbcc1..13765a5e2 100644 --- a/src/emqx_client.erl +++ b/src/emqx_client.erl @@ -1326,19 +1326,15 @@ run_sock(State = #state{socket = Sock}) -> %%------------------------------------------------------------------------------ %% Process incomming -process_incoming(<<>>, [Packet], State) -> - {keep_state, State, {next_event, cast, Packet}}; - process_incoming(<<>>, Packets, State) -> - NextEvts = [{next_event, cast, Packet} || Packet <- lists:reverse(Packets)], - {keep_state, State, NextEvts}; + {keep_state, State, next_events(Packets)}; process_incoming(Bytes, Packets, State = #state{parse_state = ParseState}) -> try emqx_frame:parse(Bytes, ParseState) of {ok, Packet, Rest} -> process_incoming(Rest, [Packet|Packets], init_parse_state(State)); {more, NewParseState} -> - {keep_state, State#state{parse_state = NewParseState}}; + {keep_state, State#state{parse_state = NewParseState}, next_events(Packets)}; {error, Reason} -> {stop, Reason} catch @@ -1346,6 +1342,13 @@ process_incoming(Bytes, Packets, State = #state{parse_state = ParseState}) -> {stop, Error} end. +next_events([]) -> + []; +next_events([Packet]) -> + {next_event, cast, Packet}; +next_events(Packets) -> + [{next_event, cast, Packet} || Packet <- lists:reverse(Packets)]. + %%------------------------------------------------------------------------------ %% Next packet id From a5f94660408a6e670a02a7a692fbc6dea8d9d18e Mon Sep 17 00:00:00 2001 From: turtleDeng Date: Fri, 11 Jan 2019 11:47:10 +0800 Subject: [PATCH 29/41] QoS0 msg support msgid (#2145) * QoS0 msg support msgid * Fix testcase --- src/emqx_message.erl | 5 +---- test/emqx_packet_SUITE.erl | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/emqx_message.erl b/src/emqx_message.erl index a4bbb378c..9763d4c62 100644 --- a/src/emqx_message.erl +++ b/src/emqx_message.erl @@ -40,7 +40,7 @@ make(From, Topic, Payload) -> -spec(make(atom() | emqx_types:client_id(), emqx_mqtt_types:qos(), emqx_topic:topic(), emqx_types:payload()) -> emqx_types:message()). make(From, QoS, Topic, Payload) -> - #message{id = msgid(QoS), + #message{id = emqx_guid:gen(), qos = QoS, from = From, flags = #{dup => false}, @@ -48,9 +48,6 @@ make(From, QoS, Topic, Payload) -> payload = Payload, timestamp = os:timestamp()}. -msgid(?QOS_0) -> undefined; -msgid(_QoS) -> emqx_guid:gen(). - set_flags(Flags, Msg = #message{flags = undefined}) when is_map(Flags) -> Msg#message{flags = Flags}; set_flags(New, Msg = #message{flags = Old}) when is_map(New) -> diff --git a/test/emqx_packet_SUITE.erl b/test/emqx_packet_SUITE.erl index 42fcbc9d7..7758da86e 100644 --- a/test/emqx_packet_SUITE.erl +++ b/test/emqx_packet_SUITE.erl @@ -102,7 +102,7 @@ packet_message(_) -> Pkt = emqx_packet:from_message(10, Msg2), Msg3 = emqx_message:set_header(username, "test", Msg2), Msg4 = emqx_packet:to_message(#{client_id => <<"clientid">>, username => "test"}, Pkt), - Msg5 = Msg4#message{timestamp = Msg3#message.timestamp}, + Msg5 = Msg4#message{timestamp = Msg3#message.timestamp, id = Msg3#message.id}, Msg5 = Msg3. packet_format(_) -> From ec03f8e1fa855f1e7a6af1351371ceb5e5a626f5 Mon Sep 17 00:00:00 2001 From: Gilbert Wong Date: Fri, 11 Jan 2019 16:28:51 +0800 Subject: [PATCH 30/41] Fix the deliver packet bug and add test case --- src/emqx_protocol.erl | 18 +++++---- test/emqx_protocol_SUITE.erl | 75 +++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index 19d1d9d09..f82e8dcad 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -392,8 +392,8 @@ process_packet(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload), PSta ?LOG(warning, "Cannot publish qos1 message to ~s for ~s", [Topic, emqx_reason_codes:text(ReasonCode)]), case deliver({puback, PacketId, ReasonCode}, PState) of - {ok, _PState} -> - do_acl_deny_action(Packet, ReasonCode, PState); + {ok, PState1} -> + do_acl_deny_action(Packet, ReasonCode, PState1); Error -> Error end @@ -406,9 +406,9 @@ process_packet(Packet = ?PUBLISH_PACKET(?QOS_2, Topic, PacketId, _Payload), PSta {error, ReasonCode} -> ?LOG(warning, "Cannot publish qos2 message to ~s for ~s", [Topic, emqx_reason_codes:text(ReasonCode)]), - case deliver({pubrec, PacketId, ?RC_NOT_AUTHORIZED}, PState) of - {ok, _PState} -> - do_acl_deny_action(Packet, ReasonCode, PState); + case deliver({pubrec, PacketId, ReasonCode}, PState) of + {ok, PState1} -> + do_acl_deny_action(Packet, ReasonCode, PState1); Error -> Error end @@ -472,8 +472,12 @@ process_packet(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters) {SubTopics, ReasonCodes} = {lists:reverse(ReverseSubTopics), lists:reverse(ReverseReasonCodes)}, ?LOG(warning, "Cannot subscribe ~p for ~p", [SubTopics, [emqx_reason_codes:text(R) || R <- ReasonCodes]]), - deliver({suback, PacketId, ReasonCodes}, PState), - do_acl_deny_action(Packet, ReasonCodes, PState) + case deliver({suback, PacketId, ReasonCodes}, PState) of + {ok, PState1} -> + do_acl_deny_action(Packet, ReasonCodes, PState1); + Error -> + Error + end end; process_packet(?UNSUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters), diff --git a/test/emqx_protocol_SUITE.erl b/test/emqx_protocol_SUITE.erl index 571019a15..eaad67a4c 100644 --- a/test/emqx_protocol_SUITE.erl +++ b/test/emqx_protocol_SUITE.erl @@ -33,6 +33,67 @@ username = <<"emqx">>, password = <<"public">>})). +-record(pstate, { + zone, + sendfun, + peername, + peercert, + proto_ver, + proto_name, + client_id, + is_assigned, + conn_pid, + conn_props, + ack_props, + username, + session, + clean_start, + topic_aliases, + packet_size, + will_topic, + will_msg, + keepalive, + mountpoint, + is_super, + is_bridge, + enable_ban, + enable_acl, + acl_deny_action, + recv_stats, + send_stats, + connected, + connected_at, + ignore_loop, + topic_alias_maximum + }). + + +-define(TEST_PSTATE(ProtoVer, SendStats), + #pstate{zone = test, + sendfun = fun(_Packet, _Options) -> ok end, + peername = test_peername, + peercert = test_peercert, + proto_ver = ProtoVer, + proto_name = <<"MQTT">>, + client_id = <<"test_pstate">>, + is_assigned = false, + conn_pid = self(), + username = <<"emqx">>, + is_super = false, + clean_start = false, + topic_aliases = #{}, + packet_size = 1000, + mountpoint = <<>>, + is_bridge = false, + enable_ban = false, + enable_acl = true, + acl_deny_action = disconnect, + recv_stats = #{msg => 0, pkt => 0}, + send_stats = SendStats, + connected = false, + ignore_loop = false, + topic_alias_maximum = #{to_client => 0, from_client => 0}}). + all() -> [ {group, mqtt_common}, @@ -55,7 +116,9 @@ groups() -> subscribe_v5]}, {acl, [sequence], - [acl_deny_action]}]. + [ + acl_deny_action_ct, + acl_deny_action_eunit]}]. init_per_suite(Config) -> [start_apps(App, SchemaFile, ConfigFile) || @@ -507,7 +570,7 @@ raw_recv_parse(P, ProtoVersion) -> emqx_frame:parse(P, {none, #{max_packet_size => ?MAX_PACKET_SIZE, version => ProtoVersion}}). -acl_deny_action(_) -> +acl_deny_action_ct(_) -> emqx_zone:set_env(external, acl_deny_action, disconnect), process_flag(trap_exit, true), [acl_deny_do_disconnect(publish, QoS, <<"acl_deny_action">>) || QoS <- lists:seq(0, 2)], @@ -515,6 +578,14 @@ acl_deny_action(_) -> emqx_zone:set_env(external, acl_deny_action, ignore), ok. +acl_deny_action_eunit(_) -> + PState = ?TEST_PSTATE(?MQTT_PROTO_V5, #{msg => 0, pkt => 0}), + CodeName = emqx_reason_codes:name(?RC_NOT_AUTHORIZED, ?MQTT_PROTO_V5), + {error, CodeName, NEWPSTATE1} = emqx_protocol:process_packet(?PUBLISH_PACKET(?QOS_1, <<"acl_deny_action">>, 1, <<"payload">>), PState), + ?assertEqual(#{pkt => 1, msg => 0}, NEWPSTATE1#pstate.send_stats), + {error, CodeName, NEWPSTATE2} = emqx_protocol:process_packet(?PUBLISH_PACKET(?QOS_2, <<"acl_deny_action">>, 2, <<"payload">>), PState), + ?assertEqual(#{pkt => 1, msg => 0}, NEWPSTATE2#pstate.send_stats). + will_check(_) -> process_flag(trap_exit, true), will_topic_check(0), From 7aa46896dfb221683f1829ac7de8814ee7fc6b0f Mon Sep 17 00:00:00 2001 From: Gilbert Wong Date: Fri, 11 Jan 2019 16:33:11 +0800 Subject: [PATCH 31/41] Fix typo --- test/emqx_protocol_SUITE.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/emqx_protocol_SUITE.erl b/test/emqx_protocol_SUITE.erl index eaad67a4c..5a9eb3735 100644 --- a/test/emqx_protocol_SUITE.erl +++ b/test/emqx_protocol_SUITE.erl @@ -116,8 +116,7 @@ groups() -> subscribe_v5]}, {acl, [sequence], - [ - acl_deny_action_ct, + [acl_deny_action_ct, acl_deny_action_eunit]}]. init_per_suite(Config) -> From 3748cd434d068331ac6ad7b3500d93ee55db5639 Mon Sep 17 00:00:00 2001 From: YoukiLin <1045735402@qq.com> Date: Sat, 12 Jan 2019 23:09:39 +0800 Subject: [PATCH 32/41] Add test case for emqx_sys_mon (#2150) --- Makefile | 2 +- test/emqx_sys_mon_SUITE.erl | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/emqx_sys_mon_SUITE.erl diff --git a/Makefile b/Makefile index 7ddc05d9f..b07092c91 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ CT_SUITES = emqx emqx_client emqx_zone emqx_banned emqx_session \ emqx_tables emqx_time emqx_topic emqx_trie emqx_vm emqx_mountpoint \ emqx_listeners emqx_protocol emqx_pool emqx_shared_sub emqx_bridge \ emqx_hooks emqx_batch emqx_sequence emqx_pmon emqx_pd emqx_gc emqx_ws_connection \ - emqx_packet emqx_connection emqx_tracer + emqx_packet emqx_connection emqx_tracer emqx_sys_mon CT_NODE_NAME = emqxct@127.0.0.1 CT_OPTS = -cover test/ct.cover.spec -erl_args -name $(CT_NODE_NAME) diff --git a/test/emqx_sys_mon_SUITE.erl b/test/emqx_sys_mon_SUITE.erl new file mode 100644 index 000000000..c014802ac --- /dev/null +++ b/test/emqx_sys_mon_SUITE.erl @@ -0,0 +1,66 @@ +%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(emqx_sys_mon_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-include_lib("common_test/include/ct.hrl"). + +-include("emqx_mqtt.hrl"). + +-define(SYSMONPID, emqx_sys_mon). +-define(INPUTINFO, [{self(), long_gc, concat_str("long_gc warning: pid = ~p, info: ~p", self(), "hello"), "hello"}, + {self(), long_schedule, concat_str("long_schedule warning: pid = ~p, info: ~p", self(), "hello"), "hello"}, + {self(), busy_port, concat_str("busy_port warning: suspid = ~p, port = ~p", self(), list_to_port("#Port<0.4>")), list_to_port("#Port<0.4>")}, + {self(), busy_dist_port, concat_str("busy_dist_port warning: suspid = ~p, port = ~p", self(), list_to_port("#Port<0.4>")),list_to_port("#Port<0.4>")}, + {list_to_port("#Port<0.4>"), long_schedule, concat_str("long_schedule warning: port = ~p, info: ~p", list_to_port("#Port<0.4>"), "hello"), "hello"} + ]). + +all() -> [t_sys_mon]. + +init_per_suite(Config) -> + emqx_ct_broker_helpers:run_setup_steps(), + Config. + +end_per_suite(_Config) -> + emqx_ct_broker_helpers:run_teardown_steps(). + +t_sys_mon(_Config) -> + lists:foreach(fun({PidOrPort, SysMonName,ValidateInfo, InfoOrPort}) -> + validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) + end, ?INPUTINFO). + +validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) -> + {ok, C} = emqx_client:start_link([{host, "localhost"}]), + {ok, _} = emqx_client:connect(C), + emqx_client:subscribe(C, emqx_topic:systop(lists:concat(['sysmon/', SysMonName])), qos1), + timer:sleep(100), + ?SYSMONPID ! {monitor, PidOrPort, SysMonName, InfoOrPort}, + receive + {publish, #{payload := Info}} -> + ?assertEqual(ValidateInfo, binary_to_list(Info)), + ct:pal("OK - received msg: ~p~n", [Info]) + after + 1000 -> + ct:fail("flase") + end. + +concat_str(ValidateInfo, InfoOrPort, Info) -> + WarnInfo = io_lib:format(ValidateInfo, [InfoOrPort, Info]), + lists:flatten(WarnInfo). + From bc31faac6b8f8f5623b8ca1be98e49e97ec87705 Mon Sep 17 00:00:00 2001 From: tigercl Date: Thu, 17 Jan 2019 13:57:42 +0800 Subject: [PATCH 33/41] Fix will msg (#2156) * Remove will_msg and will_topic from protocol state * Modify try_open_session/1 --- src/emqx_protocol.erl | 54 ++++++++++++++----------------- src/emqx_session.erl | 16 +++++++-- test/emqx_connection_SUITE.erl | 1 - test/emqx_protocol_SUITE.erl | 18 +++++------ test/emqx_ws_connection_SUITE.erl | 1 - 5 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index f82e8dcad..87bf50d08 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -50,8 +50,6 @@ clean_start, topic_aliases, packet_size, - will_topic, - will_msg, keepalive, mountpoint, is_super, @@ -130,13 +128,11 @@ info(PState = #pstate{conn_props = ConnProps, ack_props = AckProps, session = Session, topic_aliases = Aliases, - will_msg = WillMsg, enable_acl = EnableAcl}) -> attrs(PState) ++ [{conn_props, ConnProps}, {ack_props, AckProps}, {session, Session}, {topic_aliases, Aliases}, - {will_msg, WillMsg}, {enable_acl, EnableAcl}]. attrs(#pstate{zone = Zone, @@ -349,11 +345,11 @@ process_packet(?CONNECT_PACKET( case authenticate(credentials(PState2), Password) of {ok, IsSuper} -> %% Maybe assign a clientId - PState3 = maybe_assign_client_id(PState2#pstate{is_super = IsSuper, - will_msg = make_will_msg(ConnPkt)}), + PState3 = maybe_assign_client_id(PState2#pstate{is_super = IsSuper}), emqx_logger:set_metadata_client_id(PState3#pstate.client_id), %% Open session - case try_open_session(PState3) of + SessAttrs = lists:foldl(fun set_session_attrs/2, #{will_msg => make_will_msg(ConnPkt)}, [{max_inflight, PState3}, {expiry_interval, PState3}, {misc, PState3}]), + case try_open_session(SessAttrs) of {ok, SPid, SP} -> PState4 = PState3#pstate{session = SPid, connected = true}, ok = emqx_cm:register_connection(client_id(PState4)), @@ -502,16 +498,15 @@ process_packet(?DISCONNECT_PACKET(?RC_SUCCESS, #{'Session-Expiry-Interval' := In case Interval =/= 0 andalso OldInterval =:= 0 of true -> deliver({disconnect, ?RC_PROTOCOL_ERROR}, PState), - {error, protocol_error, PState#pstate{will_msg = undefined}}; + {error, protocol_error, PState}; false -> emqx_session:update_expiry_interval(SPid, Interval), - %% Clean willmsg - {stop, normal, PState#pstate{will_msg = undefined}} + {stop, normal, PState} end; process_packet(?DISCONNECT_PACKET(?RC_SUCCESS), PState) -> - {stop, normal, PState#pstate{will_msg = undefined}}; + {stop, normal, PState}; process_packet(?DISCONNECT_PACKET(_), PState) -> - {stop, normal, PState}. + {stop, {shutdown, abnormal_disconnet}, PState}. %%------------------------------------------------------------------------------ %% ConnAck --> Client @@ -678,23 +673,13 @@ maybe_assign_client_id(PState = #pstate{client_id = <<>>, ack_props = AckProps}) maybe_assign_client_id(PState) -> PState. -try_open_session(PState = #pstate{zone = Zone, - client_id = ClientId, - conn_pid = ConnPid, - username = Username, - clean_start = CleanStart, - will_msg = WillMsg}) -> - - SessAttrs = #{ - zone => Zone, - client_id => ClientId, - conn_pid => ConnPid, - username => Username, - clean_start => CleanStart, - will_msg => WillMsg - }, - SessAttrs1 = lists:foldl(fun set_session_attrs/2, SessAttrs, [{max_inflight, PState}, {expiry_interval, PState}]), - case emqx_sm:open_session(SessAttrs1) of +try_open_session(SessAttrs = #{zone := _, + client_id := _, + conn_pid := _, + username := _, + will_msg := _, + clean_start := _}) -> + case emqx_sm:open_session(SessAttrs) of {ok, SPid} -> {ok, SPid, false}; Other -> Other @@ -722,6 +707,17 @@ set_session_attrs({topic_alias_maximum, #pstate{proto_ver = ?MQTT_PROTO_V5, conn set_session_attrs({topic_alias_maximum, #pstate{zone = Zone}}, SessAttrs) -> maps:put(topic_alias_maximum, emqx_zone:get_env(Zone, max_topic_alias, 0), SessAttrs); +set_session_attrs({misc, #pstate{zone = Zone, + client_id = ClientId, + conn_pid = ConnPid, + username = Username, + clean_start = CleanStart}}, SessAttrs) -> + SessAttrs#{zone => Zone, + client_id => ClientId, + conn_pid => ConnPid, + username => Username, + clean_start => CleanStart}; + set_session_attrs(_, SessAttrs) -> SessAttrs. diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 966ff738d..3ebcc6dfd 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -630,11 +630,21 @@ handle_info({'EXIT', ConnPid, Reason}, #state{conn_pid = ConnPid}) exit(Reason); handle_info({'EXIT', ConnPid, Reason}, State = #state{will_msg = WillMsg, expiry_interval = 0, conn_pid = ConnPid}) -> - send_willmsg(WillMsg), + case Reason of + normal -> + ignore; + _ -> + send_willmsg(WillMsg) + end, {stop, Reason, State#state{will_msg = undefined, conn_pid = undefined}}; -handle_info({'EXIT', ConnPid, _Reason}, State = #state{conn_pid = ConnPid}) -> - State1 = ensure_will_delay_timer(State), +handle_info({'EXIT', ConnPid, Reason}, State = #state{conn_pid = ConnPid}) -> + State1 = case Reason of + normal -> + State#state{will_msg = undefined}; + _ -> + ensure_will_delay_timer(State) + end, {noreply, ensure_expire_timer(State1#state{conn_pid = undefined})}; handle_info({'EXIT', OldPid, _Reason}, State = #state{old_conn_pid = OldPid}) -> diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl index 1678eab44..769f8df1f 100644 --- a/test/emqx_connection_SUITE.erl +++ b/test/emqx_connection_SUITE.erl @@ -74,7 +74,6 @@ {socktype, _}, {topic_aliases, _}, {username, _}, - {will_msg, _}, {zone, _}]). all() -> diff --git a/test/emqx_protocol_SUITE.erl b/test/emqx_protocol_SUITE.erl index 5a9eb3735..becdd60f2 100644 --- a/test/emqx_protocol_SUITE.erl +++ b/test/emqx_protocol_SUITE.erl @@ -50,8 +50,6 @@ clean_start, topic_aliases, packet_size, - will_topic, - will_msg, keepalive, mountpoint, is_super, @@ -352,7 +350,7 @@ connect_v5(_) -> will_props = #{'Will-Delay-Interval' => 5}, will_topic = <<"TopicA">>, will_payload = <<"will message">>, - properties = #{'Session-Expiry-Interval' => 3} + properties = #{'Session-Expiry-Interval' => 0} } ) ) @@ -377,11 +375,11 @@ connect_v5(_) -> {ok, ?SUBACK_PACKET(1, #{}, [2]), _} = raw_recv_parse(SubData, ?MQTT_PROTO_V5), emqx_client_sock:send(Sock, raw_send_serialize( - ?DISCONNECT_PACKET(?RC_DISCONNECT_WITH_WILL_MESSAGE) + ?DISCONNECT_PACKET(?RC_SUCCESS) ) ), - {error, timeout} = gen_tcp:recv(Sock2, 0, 1000), + {error, timeout} = gen_tcp:recv(Sock2, 0, 2000), % session resumed {ok, Sock3} = emqx_client_sock:connect({127, 0, 0, 1}, 1883, @@ -403,18 +401,20 @@ connect_v5(_) -> will_payload = <<"will message 2">>, properties = #{'Session-Expiry-Interval' => 3} } - ) + ), + #{version => ?MQTT_PROTO_V5} ) ), {ok, Data3} = gen_tcp:recv(Sock3, 0), - {ok, ?CONNACK_PACKET(?RC_SUCCESS, 1), _} = raw_recv_parse(Data3, ?MQTT_PROTO_V5), + {ok, ?CONNACK_PACKET(?RC_SUCCESS, 0), _} = raw_recv_parse(Data3, ?MQTT_PROTO_V5), emqx_client_sock:send(Sock3, raw_send_serialize( - ?DISCONNECT_PACKET(?RC_DISCONNECT_WITH_WILL_MESSAGE) + ?DISCONNECT_PACKET(?RC_DISCONNECT_WITH_WILL_MESSAGE), + #{version => ?MQTT_PROTO_V5} ) ), - {ok, WillData} = gen_tcp:recv(Sock2, 0), + {ok, WillData} = gen_tcp:recv(Sock2, 0, 5000), {ok, ?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, _, <<"will message 2">>), _} = raw_recv_parse(WillData, ?MQTT_PROTO_V5), emqx_client_sock:close(Sock2) diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl index 848e518e1..dd7b17f3e 100644 --- a/test/emqx_ws_connection_SUITE.erl +++ b/test/emqx_ws_connection_SUITE.erl @@ -56,7 +56,6 @@ {ack_props, _}, {session, _}, {topic_aliases, _}, - {will_msg, _}, {enable_acl, _}]). -define(ATTRS, [{clean_start,true}, From b8929a46c1eace13d2bcc3d1a179180a09e43e38 Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Thu, 17 Jan 2019 17:14:51 +0800 Subject: [PATCH 34/41] Fix the EXIT outputs in Travis CI (#2154) * Fix the Exit in testcases * Fix Exit in emqx_mod_sup_SUITE * Update testcases for log_tracer * Fix Exit in emqx_protocol_SUITE * Add will_acl_check * Fix more Exits --- src/emqx_client.erl | 14 ++- src/emqx_protocol.erl | 2 +- src/emqx_session.erl | 2 +- .../acl_deny_action.conf | 5 +- test/emqx_ct_broker_helpers.erl | 25 ++++ test/emqx_mod_sup_SUITE.erl | 21 +++- test/emqx_pool_SUITE.erl | 9 +- test/emqx_protocol_SUITE.erl | 119 +++++++++--------- test/emqx_sm_SUITE.erl | 53 ++++---- test/emqx_sys_mon_SUITE.erl | 3 +- test/emqx_tracer_SUITE.erl | 43 +++++-- 11 files changed, 183 insertions(+), 113 deletions(-) diff --git a/src/emqx_client.erl b/src/emqx_client.erl index 13765a5e2..1f8c2a22e 100644 --- a/src/emqx_client.erl +++ b/src/emqx_client.erl @@ -703,7 +703,7 @@ waiting_for_connack(cast, ?CONNACK_PACKET(ReasonCode, case take_call(connect, State) of {value, #call{from = From}, _State} -> Reply = {error, {Reason, Properties}}, - {stop_and_reply, Reason, [{reply, From, Reply}]}; + {stop_and_reply, {shutdown, Reason}, [{reply, From, Reply}]}; false -> {stop, connack_error} end; @@ -1004,20 +1004,24 @@ handle_event(info, {TcpOrSsL, _Sock, Data}, _StateName, State) handle_event(info, {Error, _Sock, Reason}, _StateName, State) when Error =:= tcp_error; Error =:= ssl_error -> - {stop, Reason, State}; + emqx_logger:error("[~p] ~p, Reason: ~p", [?MODULE, Error, Reason]), + {stop, {shutdown, Reason}, State}; handle_event(info, {Closed, _Sock}, _StateName, State) when Closed =:= tcp_closed; Closed =:= ssl_closed -> + emqx_logger:debug("[~p] ~p", [?MODULE, Closed]), {stop, {shutdown, Closed}, State}; handle_event(info, {'EXIT', Owner, Reason}, _, State = #state{owner = Owner}) -> - {stop, Reason, State}; + emqx_logger:debug("[~p] Got EXIT from owner, Reason: ~p", [?MODULE, Reason]), + {stop, {shutdown, Reason}, State}; handle_event(info, {inet_reply, _Sock, ok}, _, State) -> {keep_state, State}; handle_event(info, {inet_reply, _Sock, {error, Reason}}, _, State) -> - {stop, Reason, State}; + emqx_logger:error("[~p] got tcp error: ~p", [?MODULE, Reason]), + {stop, {shutdown, Reason}, State}; handle_event(EventType, EventContent, StateName, StateData) -> emqx_logger:error("State: ~s, Unexpected Event: (~p, ~p)", @@ -1305,7 +1309,7 @@ hosts(#state{hosts = Hosts}) -> Hosts. send_puback(Packet, State) -> case send(Packet, State) of {ok, NewState} -> {keep_state, NewState}; - {error, Reason} -> {stop, Reason} + {error, Reason} -> {stop, {shutdown, Reason}} end. send(Msg, State) when is_record(Msg, mqtt_msg) -> diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index 87bf50d08..8aafb313f 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -820,7 +820,7 @@ check_will_acl(#mqtt_packet_connect{will_topic = WillTopic}, PState) -> case emqx_access_control:check_acl(credentials(PState), publish, WillTopic) of allow -> ok; deny -> - ?LOG(warning, "Cannot publish will message to ~p for acl checking failed", [WillTopic]), + ?LOG(warning, "Will message (to ~s) validation failed, acl denied", [WillTopic]), {error, ?RC_UNSPECIFIED_ERROR} end. diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 3ebcc6dfd..098d76c52 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -675,7 +675,7 @@ code_change(_OldVsn, State, _Extra) -> maybe_shutdown(undefined, _Reason) -> ok; maybe_shutdown(Pid, normal) -> - Pid ! {shutdown, normal}; + Pid ! {shutdown, normal}; maybe_shutdown(Pid, Reason) -> exit(Pid, Reason). diff --git a/test/emqx_access_SUITE_data/acl_deny_action.conf b/test/emqx_access_SUITE_data/acl_deny_action.conf index 753782605..c7f933ce7 100644 --- a/test/emqx_access_SUITE_data/acl_deny_action.conf +++ b/test/emqx_access_SUITE_data/acl_deny_action.conf @@ -1,4 +1,3 @@ - {deny, {user, "emqx"}, pubsub, ["acl_deny_action"]}. - -{allow, all}. +{deny, {user, "pub_deny"}, publish, ["pub_deny"]}. +{allow, all}. \ No newline at end of file diff --git a/test/emqx_ct_broker_helpers.erl b/test/emqx_ct_broker_helpers.erl index 62a91df54..c1433c203 100644 --- a/test/emqx_ct_broker_helpers.erl +++ b/test/emqx_ct_broker_helpers.erl @@ -124,3 +124,28 @@ client_ssl_twoway() -> client_ssl() -> ?CIPHERS ++ [{reuse_sessions, true}]. + +wait_mqtt_payload(Payload) -> + receive + {publish, #{payload := Payload}} -> + ct:pal("OK - received msg: ~p~n", [Payload]) + after 1000 -> + ct:fail({timeout, Payload, {msg_box, flush()}}) + end. + +not_wait_mqtt_payload(Payload) -> + receive + {publish, #{payload := Payload}} -> + ct:fail({received, Payload}) + after 1000 -> + ct:pal("OK - msg ~p is not received", [Payload]) + end. + +flush() -> + flush([]). +flush(Msgs) -> + receive + M -> flush([M|Msgs]) + after + 0 -> lists:reverse(Msgs) + end. \ No newline at end of file diff --git a/test/emqx_mod_sup_SUITE.erl b/test/emqx_mod_sup_SUITE.erl index 8169c3f91..29b59d0b8 100644 --- a/test/emqx_mod_sup_SUITE.erl +++ b/test/emqx_mod_sup_SUITE.erl @@ -21,8 +21,23 @@ all() -> [t_child_all]. +start_link() -> + Pid = spawn_link(?MODULE, echo, [0]), + {ok, Pid}. + +echo(State) -> + receive + {From, Req} -> + ct:pal("======from:~p, req:~p", [From, Req]), + From ! Req, + echo(State) + end. + t_child_all(_) -> - {ok, _Pid} = emqx_mod_sup:start_link(), - {ok, _Child} = emqx_mod_sup:start_child(emqx_banned, worker), + {ok, Pid} = emqx_mod_sup:start_link(), + {ok, Child} = emqx_mod_sup:start_child(?MODULE, worker), timer:sleep(10), - ok = emqx_mod_sup:stop_child(emqx_banned). + Child ! {self(), hi}, + receive hi -> ok after 100 -> ct:fail({timeout, wait_echo}) end, + ok = emqx_mod_sup:stop_child(?MODULE), + exit(Pid, normal). diff --git a/test/emqx_pool_SUITE.erl b/test/emqx_pool_SUITE.erl index 36ad7c507..72fe51ec0 100644 --- a/test/emqx_pool_SUITE.erl +++ b/test/emqx_pool_SUITE.erl @@ -30,7 +30,7 @@ all() -> groups() -> [ {submit_case, [sequence], [submit_mfa, submit_fa]}, - {async_submit_case, [sequence], [async_submit_mfa, async_submit_ex]} + {async_submit_case, [sequence], [async_submit_mfa, async_submit_crash]} ]. init_per_suite(Config) -> @@ -61,8 +61,8 @@ async_submit_mfa(_Config) -> emqx_pool:async_submit({?MODULE, test_mfa, []}), emqx_pool:async_submit(fun ?MODULE:test_mfa/0, []). -async_submit_ex(_) -> - emqx_pool:async_submit(fun error_fun/0). +async_submit_crash(_) -> + emqx_pool:async_submit(fun() -> A = 1, A = 0 end). t_unexpected(_) -> Pid = emqx_pool:worker(), @@ -73,6 +73,3 @@ t_unexpected(_) -> test_mfa() -> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]). - -error_fun() -> error(test_error). - diff --git a/test/emqx_protocol_SUITE.erl b/test/emqx_protocol_SUITE.erl index becdd60f2..fc96cc839 100644 --- a/test/emqx_protocol_SUITE.erl +++ b/test/emqx_protocol_SUITE.erl @@ -101,19 +101,17 @@ all() -> ]. groups() -> - [{mqtt_common, - [sequence], - [will_check]}, - {mqttv4, - [sequence], + [{mqtt_common, [sequence], + [will_topic_check, + will_acl_check + ]}, + {mqttv4, [sequence], [connect_v4, subscribe_v4]}, - {mqttv5, - [sequence], + {mqttv5, [sequence], [connect_v5, subscribe_v5]}, - {acl, - [sequence], + {acl, [sequence], [acl_deny_action_ct, acl_deny_action_eunit]}]. @@ -266,11 +264,10 @@ connect_v5(_) -> raw_recv_parse(Data, ?MQTT_PROTO_V5), emqx_client_sock:send(Sock, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1, - qos => ?QOS_2, - rap => 0, - nl => 0, - rc => 0}}]), - #{version => ?MQTT_PROTO_V5})), + qos => ?QOS_2, + rap => 0, + nl => 0, + rc => 0}}]), #{version => ?MQTT_PROTO_V5})), {ok, Data2} = gen_tcp:recv(Sock, 0), {ok, ?SUBACK_PACKET(1, #{}, [2]), _} = raw_recv_parse(Data2, ?MQTT_PROTO_V5), @@ -365,19 +362,16 @@ connect_v5(_) -> do_connect(Sock2, ?MQTT_PROTO_V5), emqx_client_sock:send(Sock2, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1, - qos => ?QOS_2, - rap => 0, - nl => 0, - rc => 0}}]), - #{version => ?MQTT_PROTO_V5})), + qos => ?QOS_2, + rap => 0, + nl => 0, + rc => 0}}]), #{version => ?MQTT_PROTO_V5})), {ok, SubData} = gen_tcp:recv(Sock2, 0), {ok, ?SUBACK_PACKET(1, #{}, [2]), _} = raw_recv_parse(SubData, ?MQTT_PROTO_V5), emqx_client_sock:send(Sock, raw_send_serialize( - ?DISCONNECT_PACKET(?RC_SUCCESS) - ) - ), + ?DISCONNECT_PACKET(?RC_SUCCESS))), {error, timeout} = gen_tcp:recv(Sock2, 0, 2000), @@ -572,8 +566,8 @@ raw_recv_parse(P, ProtoVersion) -> acl_deny_action_ct(_) -> emqx_zone:set_env(external, acl_deny_action, disconnect), process_flag(trap_exit, true), - [acl_deny_do_disconnect(publish, QoS, <<"acl_deny_action">>) || QoS <- lists:seq(0, 2)], [acl_deny_do_disconnect(subscribe, QoS, <<"acl_deny_action">>) || QoS <- lists:seq(0, 2)], + [acl_deny_do_disconnect(publish, QoS, <<"acl_deny_action">>) || QoS <- lists:seq(0, 2)], emqx_zone:set_env(external, acl_deny_action, ignore), ok. @@ -585,57 +579,62 @@ acl_deny_action_eunit(_) -> {error, CodeName, NEWPSTATE2} = emqx_protocol:process_packet(?PUBLISH_PACKET(?QOS_2, <<"acl_deny_action">>, 2, <<"payload">>), PState), ?assertEqual(#{pkt => 1, msg => 0}, NEWPSTATE2#pstate.send_stats). -will_check(_) -> +will_topic_check(_) -> + {ok, Client} = emqx_client:start_link([{username, <<"emqx">>}, + {will_flag, true}, + {will_topic, <<"aaa">>}, + {will_payload, <<"I have died">>}, + {will_qos, 0}]), + {ok, _} = emqx_client:connect(Client), + + {ok, T} = emqx_client:start_link([{client_id, <<"client">>}]), + emqx_client:connect(T), + emqx_client:subscribe(T, <<"aaa">>), + ct:sleep(200), + + emqx_client:stop(Client), + ct:sleep(100), + false = is_process_alive(Client), + emqx_ct_broker_helpers:wait_mqtt_payload(<<"I have died">>), + emqx_client:stop(T). + +will_acl_check(_) -> + %% The connection will be rejected if publishing of the will message is not allowed by + %% ACL rules process_flag(trap_exit, true), - will_topic_check(0), - will_acl_check(0). - -will_topic_check(QoS) -> - {ok, Client} = emqx_client:start_link([{username, <<"emqx">>}, + {ok, Client} = emqx_client:start_link([{username, <<"pub_deny">>}, {will_flag, true}, - {will_topic, <<"">>}, + {will_topic, <<"pub_deny">>}, {will_payload, <<"I have died">>}, - {will_qos, QoS}]), - try emqx_client:connect(Client) of - _ -> - ok - catch - exit : _Reason -> - false = is_process_alive(Client) - end. - -will_acl_check(QoS) -> - {ok, Client} = emqx_client:start_link([{username, <<"emqx">>}, - {will_flag, true}, - {will_topic, <<"acl_deny_action">>}, - {will_payload, <<"I have died">>}, - {will_qos, QoS}]), - try emqx_client:connect(Client) of - _ -> - ok - catch - exit : _Reason -> - false = is_process_alive(Client) - end. + {will_qos, 0}]), + ?assertMatch({error,{_,_}}, emqx_client:connect(Client)). acl_deny_do_disconnect(publish, QoS, Topic) -> + process_flag(trap_exit, true), {ok, Client} = emqx_client:start_link([{username, <<"emqx">>}]), {ok, _} = emqx_client:connect(Client), emqx_client:publish(Client, Topic, <<"test">>, QoS), receive - {'EXIT', Client, _Reason} -> - false = is_process_alive(Client) + {'EXIT', Client, {shutdown,tcp_closed}} -> + ct:pal(info, "[OK] after publish, received exit: {shutdown,tcp_closed}"), + false = is_process_alive(Client); + {'EXIT', Client, Reason} -> + ct:pal(info, "[OK] after publish, client got disconnected: ~p", [Reason]) + after 1000 -> ct:fail({timeout, wait_tcp_closed}) end; acl_deny_do_disconnect(subscribe, QoS, Topic) -> + process_flag(trap_exit, true), {ok, Client} = emqx_client:start_link([{username, <<"emqx">>}]), {ok, _} = emqx_client:connect(Client), - try emqx_client:subscribe(Client, Topic, QoS) of - _ -> - ok - catch - exit : _Reason -> - false = is_process_alive(Client) + {ok, _, [128]} = emqx_client:subscribe(Client, Topic, QoS), + receive + {'EXIT', Client, {shutdown,tcp_closed}} -> + ct:pal(info, "[OK] after subscribe, received exit: {shutdown,tcp_closed}"), + false = is_process_alive(Client); + {'EXIT', Client, Reason} -> + ct:pal(info, "[OK] after subscribe, client got disconnected: ~p", [Reason]) + after 1000 -> ct:fail({timeout, wait_tcp_closed}) end. start_apps(App, SchemaFile, ConfigFile) -> diff --git a/test/emqx_sm_SUITE.erl b/test/emqx_sm_SUITE.erl index b3ce70c82..3d1db5ddc 100644 --- a/test/emqx_sm_SUITE.erl +++ b/test/emqx_sm_SUITE.erl @@ -16,6 +16,7 @@ -include("emqx.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -compile(export_all). -compile(nowarn_export_all). @@ -33,7 +34,7 @@ all() -> [{group, sm}]. groups() -> [{sm, [non_parallel_tests], - [t_open_close_session, + [ t_resume_session, t_discard_session, t_register_unregister_session, @@ -48,45 +49,47 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_broker_helpers:run_teardown_steps(). -t_open_close_session(_) -> - {ok, ClientPid} = emqx_mock_client:start_link(<<"client">>), - {ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => ClientPid}), - ?assertEqual(ok, emqx_sm:close_session(SPid)). +init_per_testcase(_All, Config) -> + {ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => self()}), + [{session_pid, SPid}|Config]. -t_resume_session(_) -> - {ok, ClientPid} = emqx_mock_client:start_link(<<"client">>), - {ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => ClientPid}), - ?assertEqual({ok, SPid}, emqx_sm:resume_session(<<"client">>, ?ATTRS#{conn_pid => ClientPid})). +end_per_testcase(_All, Config) -> + emqx_sm:close_session(?config(session_pid, Config)), + receive + {shutdown, normal} -> ok + after 500 -> ct:fail({timeout, wait_session_shutdown}) + end. + +t_resume_session(Config) -> + ?assertEqual({ok, ?config(session_pid, Config)}, emqx_sm:resume_session(<<"client">>, ?ATTRS#{conn_pid => self()})). t_discard_session(_) -> - {ok, ClientPid} = emqx_mock_client:start_link(<<"client1">>), - {ok, _SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => ClientPid}), ?assertEqual(ok, emqx_sm:discard_session(<<"client1">>)). t_register_unregister_session(_) -> Pid = self(), - {ok, _ClientPid} = emqx_mock_client:start_link(<<"client">>), ?assertEqual(ok, emqx_sm:register_session(<<"client">>)), ?assertEqual(ok, emqx_sm:register_session(<<"client">>, Pid)), ?assertEqual(ok, emqx_sm:unregister_session(<<"client">>)), ?assertEqual(ok, emqx_sm:unregister_session(<<"client">>), Pid). -t_get_set_session_attrs(_) -> - {ok, ClientPid} = emqx_mock_client:start_link(<<"client">>), - {ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => ClientPid}), - ?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, [?ATTRS#{conn_pid => ClientPid}])), - ?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, SPid, [?ATTRS#{conn_pid => ClientPid}])), - [SAttr] = emqx_sm:get_session_attrs(<<"client">>, SPid), - ?assertEqual(<<"client">>, maps:get(client_id, SAttr)). +t_get_set_session_attrs(Config) -> + SPid = ?config(session_pid, Config), + ClientPid0 = spawn(fun() -> receive _ -> ok end end), + ?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, [?ATTRS#{conn_pid => ClientPid0}])), + ?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, SPid, [?ATTRS#{conn_pid => ClientPid0}])), + [SAttr0] = emqx_sm:get_session_attrs(<<"client">>, SPid), + ?assertEqual(ClientPid0, maps:get(conn_pid, SAttr0)), + ?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, SPid, [?ATTRS#{conn_pid => self()}])), + [SAttr1] = emqx_sm:get_session_attrs(<<"client">>, SPid), + ?assertEqual(self(), maps:get(conn_pid, SAttr1)). -t_get_set_session_stats(_) -> - {ok, ClientPid} = emqx_mock_client:start_link(<<"client">>), - {ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => ClientPid}), +t_get_set_session_stats(Config) -> + SPid = ?config(session_pid, Config), ?assertEqual(true, emqx_sm:set_session_stats(<<"client">>, [{inflight, 10}])), ?assertEqual(true, emqx_sm:set_session_stats(<<"client">>, SPid, [{inflight, 10}])), ?assertEqual([{inflight, 10}], emqx_sm:get_session_stats(<<"client">>, SPid)). -t_lookup_session_pids(_) -> - {ok, ClientPid} = emqx_mock_client:start_link(<<"client">>), - {ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => ClientPid}), +t_lookup_session_pids(Config) -> + SPid = ?config(session_pid, Config), ?assertEqual([SPid], emqx_sm:lookup_session_pids(<<"client">>)). diff --git a/test/emqx_sys_mon_SUITE.erl b/test/emqx_sys_mon_SUITE.erl index c014802ac..e2e9fe2ad 100644 --- a/test/emqx_sys_mon_SUITE.erl +++ b/test/emqx_sys_mon_SUITE.erl @@ -58,7 +58,8 @@ validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) -> after 1000 -> ct:fail("flase") - end. + end, + emqx_client:stop(C). concat_str(ValidateInfo, InfoOrPort, Info) -> WarnInfo = io_lib:format(ValidateInfo, [InfoOrPort, Info]), diff --git a/test/emqx_tracer_SUITE.erl b/test/emqx_tracer_SUITE.erl index b8ec8e21c..d1a3bfa5e 100644 --- a/test/emqx_tracer_SUITE.erl +++ b/test/emqx_tracer_SUITE.erl @@ -36,12 +36,39 @@ start_traces(_Config) -> {username, <<"testuser">>}, {password, <<"pass">>}]), emqx_client:connect(T), - emqx_client:subscribe(T, <<"a/b/c">>), - ok = emqx_tracer:start_trace({client_id, <<"client">>}, all, "test/emqx_SUITE_data/clientid_trace.log"), - ok = emqx_tracer:start_trace({topic, <<"topic">>}, all, "test/emqx_SUITE_data/topic_trace.log"), - {ok, _} = file:read_file("test/emqx_SUITE_data/clientid_trace.log"), - {ok, _} = file:read_file("test/emqx_SUITE_data/topic_trace.log"), - Result = emqx_tracer:lookup_traces(), - ?assertEqual([{{client_id,<<"client">>},{all,"test/emqx_SUITE_data/clientid_trace.log"}},{{topic,<<"topic">>},{all,"test/emqx_SUITE_data/topic_trace.log"}}], Result), + + %% Start tracing + ok = emqx_tracer:start_trace({client_id, <<"client">>}, debug, "tmp/client.log"), + ok = emqx_tracer:start_trace({client_id, <<"client2">>}, all, "tmp/client2.log"), + ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"), + ct:sleep(100), + + %% Verify the tracing file exits + ?assert(filelib:is_regular("tmp/client.log")), + ?assert(filelib:is_regular("tmp/client2.log")), + ?assert(filelib:is_regular("tmp/topic_trace.log")), + + %% Get current traces + ?assertEqual([{{client_id,<<"client">>},{debug,"tmp/client.log"}}, + {{client_id,<<"client2">>},{all,"tmp/client2.log"}}, + {{topic,<<"a/#">>},{all,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()), + + %% set the overall log level to debug + emqx_logger:set_log_level(debug), + + %% Client with clientid = "client" publishes a "hi" message to "a/b/c". + emqx_client:publish(T, <<"a/b/c">>, <<"hi">>), + ct:sleep(200), + + %% Verify messages are logged to "tmp/client.log" and "tmp/topic_trace.log", but not "tmp/client2.log". + ?assert(filelib:file_size("tmp/client.log") > 0), + ?assert(filelib:file_size("tmp/topic_trace.log") > 0), + ?assert(filelib:file_size("tmp/client2.log") == 0), + + %% Stop tracing ok = emqx_tracer:stop_trace({client_id, <<"client">>}), - ok = emqx_tracer:stop_trace({topic, <<"topic">>}). + ok = emqx_tracer:stop_trace({client_id, <<"client2">>}), + ok = emqx_tracer:stop_trace({topic, <<"a/#">>}), + emqx_client:disconnect(T), + + emqx_logger:set_log_level(error). \ No newline at end of file From 067d28dcb6d93594975f44f58922c91d106be82b Mon Sep 17 00:00:00 2001 From: Gilbert Date: Mon, 21 Jan 2019 09:49:29 +0800 Subject: [PATCH 35/41] Change the reason code in will topic acl check (#2168) This chang the reason code to not authorized code. --- src/emqx_protocol.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index 8aafb313f..19a1dd838 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -708,7 +708,7 @@ set_session_attrs({topic_alias_maximum, #pstate{zone = Zone}}, SessAttrs) -> maps:put(topic_alias_maximum, emqx_zone:get_env(Zone, max_topic_alias, 0), SessAttrs); set_session_attrs({misc, #pstate{zone = Zone, - client_id = ClientId, + client_id = ClientId, conn_pid = ConnPid, username = Username, clean_start = CleanStart}}, SessAttrs) -> @@ -821,7 +821,7 @@ check_will_acl(#mqtt_packet_connect{will_topic = WillTopic}, PState) -> allow -> ok; deny -> ?LOG(warning, "Will message (to ~s) validation failed, acl denied", [WillTopic]), - {error, ?RC_UNSPECIFIED_ERROR} + {error, ?RC_NOT_AUTHORIZED} end. check_publish(Packet, PState) -> From 55ec358cd6e080622233ca637317deaec8287abf Mon Sep 17 00:00:00 2001 From: Gilbert Date: Tue, 22 Jan 2019 09:42:32 +0800 Subject: [PATCH 36/41] Fix bridge bug (#2160) * Fix bridge bug * Fix ack bug * Limit bridge QoS less than 1 --- src/emqx_bridge.erl | 57 ++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index 133b3b621..5d3ae3e91 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -114,12 +114,12 @@ init([Options]) -> ReconnectInterval = get_value(reconnect_interval, Options, 30000), Mountpoint = format_mountpoint(get_value(mountpoint, Options)), QueueOptions = get_value(queue, Options), - {ok, #state{mountpoint = Mountpoint, - queue_option = QueueOptions, - readq = [], - writeq = [], - options = Options, - reconnect_interval = ReconnectInterval}}. + {ok, #state{mountpoint = Mountpoint, + queue_option = QueueOptions, + readq = [], + writeq = [], + options = Options, + reconnect_interval = ReconnectInterval}}. handle_call(start_bridge, _From, State = #state{client_pid = undefined}) -> {Msg, NewState} = bridge(start, State), @@ -228,16 +228,19 @@ handle_info(replay, State = #state{client_pid = ClientPid, readq = ReadQ}) -> %%---------------------------------------------------------------- %% received local node message %%---------------------------------------------------------------- -handle_info({dispatch, _, #message{topic = Topic, payload = Payload, flags = #{retain := Retain}}}, +handle_info({dispatch, _, #message{topic = Topic, qos = QoS, payload = Payload, flags = #{retain := Retain}}}, State = #state{client_pid = undefined, - mountpoint = Mountpoint}) -> + mountpoint = Mountpoint}) + when QoS =< 1 -> Msg = #mqtt_msg{qos = 1, retain = Retain, topic = mountpoint(Mountpoint, Topic), payload = Payload}, {noreply, en_writeq({undefined, Msg}, State)}; -handle_info({dispatch, _, #message{topic = Topic, payload = Payload, flags = #{retain := Retain}}}, - State = #state{client_pid = Pid, mountpoint = Mountpoint}) -> +handle_info({dispatch, _, #message{topic = Topic, qos = QoS ,payload = Payload, flags = #{retain := Retain}}}, + State = #state{client_pid = Pid, + mountpoint = Mountpoint}) + when QoS =< 1 -> Msg = #mqtt_msg{qos = 1, retain = Retain, topic = mountpoint(Mountpoint, Topic), @@ -347,7 +350,6 @@ format_mountpoint(undefined) -> format_mountpoint(Prefix) -> binary:replace(bin(Prefix), <<"${node}">>, atom_to_binary(node(), utf8)). - en_writeq(Msg, State = #state{replayq = ReplayQ, queue_option = #{mem_cache := false}}) -> NewReplayQ = replayq:append(ReplayQ, [Msg]), @@ -369,16 +371,21 @@ publish_readq_msg(ClientPid, [{_PktId, Msg} | ReadQ], NewReadQ) -> publish_readq_msg(ClientPid, ReadQ, [{PktId, Msg} | NewReadQ]). delete(PktId, State = #state{ replayq = ReplayQ, - queue_option = #{ mem_cache := false }}) -> + readq = [], + queue_option = #{ mem_cache := false}}) -> {NewReplayQ, NewAckRef, Msgs} = replayq:pop(ReplayQ, #{count_limit => 1}), + logger:debug("[Msg] PacketId ~p, Msg: ~p", [PktId, Msgs]), + ok = replayq:ack(NewReplayQ, NewAckRef), case Msgs of - [{PktId, Msg}] -> - logger:debug("[Msg] PacketId ~p, Msg: ~p", [PktId, Msg]), - replayq:ack(ReplayQ, NewAckRef), - State#state{ replayq = NewReplayQ, ackref = NewAckRef}; - _ -> + [{PktId, _Msg}] -> self() ! pop, - State + State#state{ replayq = NewReplayQ, ackref = NewAckRef }; + [{_PktId, _Msg}] -> + NewReplayQ1 = replayq:append(NewReplayQ, Msgs), + self() ! pop, + State#state{ replayq = NewReplayQ1, ackref = NewAckRef }; + _Empty -> + State#state{ replayq = NewReplayQ, ackref = NewAckRef} end; delete(_PktId, State = #state{readq = [], writeq = [], replayq = ReplayQ, ackref = AckRef}) -> ok = replayq:ack(ReplayQ, AckRef), @@ -388,8 +395,16 @@ delete(_PktId, State = #state{readq = [], writeq = [], replayq = ReplayQ, ackref delete(PktId, State = #state{readq = [], writeq = WriteQ}) -> State#state{writeq = lists:keydelete(PktId, 1, WriteQ)}; -delete(PktId, State = #state{readq = ReadQ}) -> - State#state{readq = lists:keydelete(PktId, 1, ReadQ)}. +delete(PktId, State = #state{readq = ReadQ, replayq = ReplayQ, ackref = AckRef}) -> + NewReadQ = lists:keydelete(PktId, 1, ReadQ), + case NewReadQ of + [] -> + ok = replayq:ack(ReplayQ, AckRef), + self() ! pop; + _NewReadQ -> + ok + end, + State#state{ readq = NewReadQ }. bridge(Action, State = #state{options = Options, replayq = ReplayQ, @@ -397,7 +412,7 @@ bridge(Action, State = #state{options = Options, = QueueOption = #{batch_size := BatchSize}}) when BatchSize > 0 -> - case emqx_client:start_link([{owner, self()}|options(Options)]) of + case emqx_client:start_link([{owner, self()} | options(Options)]) of {ok, ClientPid} -> case emqx_client:connect(ClientPid) of {ok, _} -> From a6f138b55c2a308abf246094c714f335ddd38292 Mon Sep 17 00:00:00 2001 From: spring2maz <40776645+spring2maz@users.noreply.github.com> Date: Tue, 22 Jan 2019 02:57:37 +0100 Subject: [PATCH 37/41] Improve shared sub dispatch implementation. (#2144) Before this change, when shared dispatch ack is enabled (in config) in case all subscribers are offline (all sessions gave negative ack) the message is simply discarded. In this change, it is ensured to have one session picked according to configured dispatch strategy when no subscriber is online. The messages dispatched in such scenario are then queued in session state. --- src/emqx_session.erl | 2 +- src/emqx_shared_sub.erl | 48 +++++++++++++++++++--------------- test/emqx_shared_sub_SUITE.erl | 17 ++++++++++-- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 098d76c52..689d84b29 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -184,7 +184,7 @@ info(State = #state{conn_pid = ConnPid, {upgrade_qos, UpgradeQoS}, {inflight, Inflight}, {retry_interval, RetryInterval}, - {mqueue_len, MQueue}, + {mqueue_len, emqx_mqueue:len(MQueue)}, {awaiting_rel, AwaitingRel}, {max_awaiting_rel, MaxAwaitingRel}, {await_rel_timeout, AwaitRelTimeout}]. diff --git a/src/emqx_shared_sub.erl b/src/emqx_shared_sub.erl index 67f03a93c..e14963bc7 100644 --- a/src/emqx_shared_sub.erl +++ b/src/emqx_shared_sub.erl @@ -91,18 +91,12 @@ dispatch(Group, Topic, Delivery = #delivery{message = Msg, results = Results}, F case pick(strategy(), ClientId, Group, Topic, FailedSubs) of false -> Delivery; - SubPid -> - case do_dispatch(SubPid, Topic, Msg) of + {Type, SubPid} -> + case do_dispatch(SubPid, Topic, Msg, Type) of ok -> Delivery#delivery{results = [{dispatch, {Group, Topic}, 1} | Results]}; {error, _Reason} -> - %% failed to dispatch to this sub, try next - %% 'Reason' is discarded so far, meaning for QoS1/2 messages - %% if all subscribers are off line, the dispatch would faile - %% even if there are sessions not expired yet. - %% If required, we can make use of the 'no_connection' reason to perform - %% retry without requiring acks, so the messages can be delivered - %% to sessions of offline clients + %% Failed to dispatch to this sub, try next. dispatch(Group, Topic, Delivery, [SubPid | FailedSubs]) end end. @@ -115,19 +109,23 @@ strategy() -> ack_enabled() -> emqx_config:get_env(shared_dispatch_ack_enabled, false). -do_dispatch(SubPid, Topic, Msg) when SubPid =:= self() -> +do_dispatch(SubPid, Topic, Msg, _Type) when SubPid =:= self() -> %% Deadlock otherwise _ = erlang:send(SubPid, {dispatch, Topic, Msg}), ok; -do_dispatch(SubPid, Topic, Msg) -> - dispatch_per_qos(SubPid, Topic, Msg). +do_dispatch(SubPid, Topic, Msg, Type) -> + dispatch_per_qos(SubPid, Topic, Msg, Type). %% return either 'ok' (when everything is fine) or 'error' -dispatch_per_qos(SubPid, Topic, #message{qos = ?QOS_0} = Msg) -> +dispatch_per_qos(SubPid, Topic, #message{qos = ?QOS_0} = Msg, _Type) -> %% For QoS 0 message, send it as regular dispatch _ = erlang:send(SubPid, {dispatch, Topic, Msg}), ok; -dispatch_per_qos(SubPid, Topic, Msg) -> +dispatch_per_qos(SubPid, Topic, Msg, retry) -> + %% Retry implies all subscribers nack:ed, send again without ack + _ = erlang:send(SubPid, {dispatch, Topic, Msg}), + ok; +dispatch_per_qos(SubPid, Topic, Msg, fresh) -> case ack_enabled() of true -> dispatch_with_ack(SubPid, Topic, Msg); @@ -211,24 +209,32 @@ pick(sticky, ClientId, Group, Topic, FailedSubs) -> true -> %% the old subscriber is still alive %% keep using it for sticky strategy - Sub0; + {fresh, Sub0}; false -> %% randomly pick one for the first message - Sub = do_pick(random, ClientId, Group, Topic, FailedSubs), + {Type, Sub} = do_pick(random, ClientId, Group, Topic, [Sub0 | FailedSubs]), %% stick to whatever pick result erlang:put({shared_sub_sticky, Group, Topic}, Sub), - Sub + {Type, Sub} end; pick(Strategy, ClientId, Group, Topic, FailedSubs) -> do_pick(Strategy, ClientId, Group, Topic, FailedSubs). do_pick(Strategy, ClientId, Group, Topic, FailedSubs) -> - case subscribers(Group, Topic) -- FailedSubs of - [] -> false; - [Sub] -> Sub; - All -> pick_subscriber(Group, Topic, Strategy, ClientId, All) + All = subscribers(Group, Topic), + case All -- FailedSubs of + [] when FailedSubs =:= [] -> + %% Genuinely no subscriber + false; + [] -> + %% All offline? pick one anyway + {retry, pick_subscriber(Group, Topic, Strategy, ClientId, All)}; + Subs -> + %% More than one available + {fresh, pick_subscriber(Group, Topic, Strategy, ClientId, Subs)} end. +pick_subscriber(_Group, _Topic, _Strategy, _ClientId, [Sub]) -> Sub; pick_subscriber(Group, Topic, Strategy, ClientId, Subs) -> Nth = do_pick_subscriber(Group, Topic, Strategy, ClientId, length(Subs)), lists:nth(Nth, Subs). diff --git a/test/emqx_shared_sub_SUITE.erl b/test/emqx_shared_sub_SUITE.erl index dd0e20ad1..cc5a5f515 100644 --- a/test/emqx_shared_sub_SUITE.erl +++ b/test/emqx_shared_sub_SUITE.erl @@ -72,6 +72,9 @@ t_random_basic(_) -> %% out which member it picked, then close its connection %% send the second message, the message should be 'nack'ed %% by the sticky session and delivered to the 2nd session. +%% After the connection for the 2nd session is also closed, +%% i.e. when all clients are offline, the following message(s) +%% should be delivered randomly. t_no_connection_nack(_) -> ok = ensure_config(sticky), Publisher = <<"publisher">>, @@ -117,7 +120,7 @@ t_no_connection_nack(_) -> %% sleep then make synced calls to session processes to ensure that %% the connection pid's 'EXIT' message is propagated to the session process %% also to be sure sessions are still alive - timer:sleep(5), + timer:sleep(2), _ = emqx_session:info(SPid1), _ = emqx_session:info(SPid2), %% Now we know what is the other still alive connection @@ -128,11 +131,21 @@ t_no_connection_nack(_) -> SendF(Id), ?wait(Received(Id, TheOtherConnPid), 1000) end, PacketIdList), + %% Now close the 2nd (last connection) + emqx_mock_client:stop(TheOtherConnPid), + timer:sleep(2), + %% both sessions should have conn_pid = undefined + ?assertEqual({conn_pid, undefined}, lists:keyfind(conn_pid, 1, emqx_session:info(SPid1))), + ?assertEqual({conn_pid, undefined}, lists:keyfind(conn_pid, 1, emqx_session:info(SPid2))), + %% send more messages, but all should be queued in session state + lists:foreach(fun(Id) -> SendF(Id) end, PacketIdList), + {_, L1} = lists:keyfind(mqueue_len, 1, emqx_session:info(SPid1)), + {_, L2} = lists:keyfind(mqueue_len, 1, emqx_session:info(SPid2)), + ?assertEqual(length(PacketIdList), L1 + L2), %% clean up emqx_mock_client:close_session(PubConnPid), emqx_sm:close_session(SPid1), emqx_sm:close_session(SPid2), - emqx_mock_client:close_session(TheOtherConnPid), ok. t_random(_) -> From 88dbbc3a44e2ede95ac87fc32da00b5bbf4d5a54 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 23 Jan 2019 10:47:23 +0800 Subject: [PATCH 38/41] Upgrade ekka, esockd libraries --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b07092c91..16c9795b1 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ DEPS = jsx gproc gen_rpc ekka esockd cowboy replayq dep_jsx = hex-emqx 2.9.0 dep_gproc = hex-emqx 0.8.0 dep_gen_rpc = git-emqx https://github.com/emqx/gen_rpc 2.3.0 -dep_esockd = git-emqx https://github.com/emqx/esockd v5.4.3 -dep_ekka = git-emqx https://github.com/emqx/ekka v0.5.1 +dep_esockd = git-emqx https://github.com/emqx/esockd v5.4.4 +dep_ekka = git-emqx https://github.com/emqx/ekka v0.5.3 dep_cowboy = hex-emqx 2.4.0 dep_replayq = git-emqx https://github.com/emqx/replayq v0.1.1 From bb9c41c9f0141fc7d38c1406317785e54599ee7d Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 23 Jan 2019 11:00:30 +0800 Subject: [PATCH 39/41] Update rebar.config --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index 993790aa9..7486e7267 100644 --- a/rebar.config +++ b/rebar.config @@ -6,9 +6,9 @@ %% appended to deps in rebar.config.script {github_emqx_deps, [{gen_rpc, "2.3.0"}, - {ekka, "v0.5.1"}, + {ekka, "v0.5.3"}, {replayq, "v0.1.1"}, - {esockd, "v5.4.3"}, + {esockd, "v5.4.4"}, {cuttlefish, "v2.2.1"} ]}. From d5b17c516e28bc7e2881c7ef0eb82736d80e682f Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 24 Jan 2019 10:34:54 +0800 Subject: [PATCH 40/41] Improve the 'try_open_session' function --- src/emqx_protocol.erl | 86 +++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index 19a1dd838..9b9ef7f7d 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -21,6 +21,7 @@ -export([init/2]). -export([info/1]). -export([attrs/1]). +-export([attr/2]). -export([caps/1]). -export([stats/1]). -export([client_id/1]). @@ -162,6 +163,28 @@ attrs(#pstate{zone = Zone, {is_bridge, IsBridge}, {connected_at, ConnectedAt}]. +attr(max_inflight, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}) -> + get_property('Receive-Maximum', ConnProps, 65535); +attr(max_inflight, #pstate{zone = Zone}) -> + emqx_zone:get_env(Zone, max_inflight, 65535); +attr(expiry_interval, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}) -> + get_property('Session-Expiry-Interval', ConnProps, 0); +attr(expiry_interval, #pstate{zone = Zone, clean_start = CleanStart}) -> + case CleanStart of + true -> 0; + false -> emqx_zone:get_env(Zone, session_expiry_interval, 16#ffffffff) + end; +attr(topic_alias_maximum, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}) -> + get_property('Topic-Alias-Maximum', ConnProps, 0); +attr(topic_alias_maximum, #pstate{zone = Zone}) -> + emqx_zone:get_env(Zone, max_topic_alias, 0); +attr(Name, PState) -> + Attrs = lists:zip(record_info(fields, pstate), tl(tuple_to_list(PState))), + case lists:keyfind(Name, 1, Attrs) of + {_, Value} -> Value; + false -> undefined + end. + caps(#pstate{zone = Zone}) -> emqx_mqtt_caps:get_caps(Zone). @@ -348,8 +371,8 @@ process_packet(?CONNECT_PACKET( PState3 = maybe_assign_client_id(PState2#pstate{is_super = IsSuper}), emqx_logger:set_metadata_client_id(PState3#pstate.client_id), %% Open session - SessAttrs = lists:foldl(fun set_session_attrs/2, #{will_msg => make_will_msg(ConnPkt)}, [{max_inflight, PState3}, {expiry_interval, PState3}, {misc, PState3}]), - case try_open_session(SessAttrs) of + SessAttrs = #{will_msg => make_will_msg(ConnPkt)}, + case try_open_session(SessAttrs, PState3) of {ok, SPid, SP} -> PState4 = PState3#pstate{session = SPid, connected = true}, ok = emqx_cm:register_connection(client_id(PState4)), @@ -673,54 +696,26 @@ maybe_assign_client_id(PState = #pstate{client_id = <<>>, ack_props = AckProps}) maybe_assign_client_id(PState) -> PState. -try_open_session(SessAttrs = #{zone := _, - client_id := _, - conn_pid := _, - username := _, - will_msg := _, - clean_start := _}) -> - case emqx_sm:open_session(SessAttrs) of +try_open_session(SessAttrs, PState = #pstate{zone = Zone, + client_id = ClientId, + conn_pid = ConnPid, + username = Username, + clean_start = CleanStart}) -> + case emqx_sm:open_session( + maps:merge(#{zone => Zone, + client_id => ClientId, + conn_pid => ConnPid, + username => Username, + clean_start => CleanStart, + max_inflight => attr(max_inflight, PState), + expiry_interval => attr(expiry_interval, PState), + topic_alias_maximum => attr(topic_alias_maximum, PState)}, + SessAttrs)) of {ok, SPid} -> {ok, SPid, false}; Other -> Other end. - -set_session_attrs({max_inflight, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}}, SessAttrs) -> - maps:put(max_inflight, get_property('Receive-Maximum', ConnProps, 65535), SessAttrs); - -set_session_attrs({max_inflight, #pstate{zone = Zone}}, SessAttrs) -> - maps:put(max_inflight, emqx_zone:get_env(Zone, max_inflight, 65535), SessAttrs); - -set_session_attrs({expiry_interval, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}}, SessAttrs) -> - maps:put(expiry_interval, get_property('Session-Expiry-Interval', ConnProps, 0), SessAttrs); - -set_session_attrs({expiry_interval, #pstate{zone = Zone, clean_start = CleanStart}}, SessAttrs) -> - maps:put(expiry_interval, case CleanStart of - true -> 0; - false -> emqx_zone:get_env(Zone, session_expiry_interval, 16#ffffffff) - end, SessAttrs); - -set_session_attrs({topic_alias_maximum, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}}, SessAttrs) -> - maps:put(topic_alias_maximum, get_property('Topic-Alias-Maximum', ConnProps, 0), SessAttrs); - -set_session_attrs({topic_alias_maximum, #pstate{zone = Zone}}, SessAttrs) -> - maps:put(topic_alias_maximum, emqx_zone:get_env(Zone, max_topic_alias, 0), SessAttrs); - -set_session_attrs({misc, #pstate{zone = Zone, - client_id = ClientId, - conn_pid = ConnPid, - username = Username, - clean_start = CleanStart}}, SessAttrs) -> - SessAttrs#{zone => Zone, - client_id => ClientId, - conn_pid => ConnPid, - username => Username, - clean_start => CleanStart}; - -set_session_attrs(_, SessAttrs) -> - SessAttrs. - authenticate(Credentials, Password) -> case emqx_access_control:authenticate(Credentials, Password) of ok -> {ok, false}; @@ -978,3 +973,4 @@ reason_codes_compat(unsuback, _ReasonCodes, _ProtoVer) -> undefined; reason_codes_compat(PktType, ReasonCodes, _ProtoVer) -> [emqx_reason_codes:compat(PktType, RC) || RC <- ReasonCodes]. + From b461e26f252e685bfa8fe6a23cd734bd55b53869 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Thu, 24 Jan 2019 23:55:57 +0800 Subject: [PATCH 41/41] Reload config (#2180) Reload config when restart or reboot emqx --- src/emqx.erl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/emqx.erl b/src/emqx.erl index 76e966a59..728b35cfc 100644 --- a/src/emqx.erl +++ b/src/emqx.erl @@ -17,7 +17,7 @@ -include("emqx.hrl"). %% Start/Stop the application --export([start/0, is_running/1, stop/0]). +-export([start/0, restart/1, is_running/1, stop/0]). %% PubSub API -export([subscribe/1, subscribe/2, subscribe/3]). @@ -47,6 +47,12 @@ start() -> %% Check Mnesia application:ensure_all_started(?APP). +-spec(restart(string()) -> ok). +restart(ConfFile) -> + reload_config(ConfFile), + shutdown(), + reboot(). + %% @doc Stop emqx application. -spec(stop() -> ok | {error, term()}). stop() -> @@ -158,3 +164,11 @@ shutdown(Reason) -> reboot() -> lists:foreach(fun application:start/1, [gproc, esockd, ranch, cowboy, ekka, emqx]). +%%------------------------------------------------------------------------------ +%% Internal functions +%%------------------------------------------------------------------------------ +reload_config(ConfFile) -> + {ok, [Conf]} = file:consult(ConfFile), + lists:foreach(fun({App, Vals}) -> + [application:set_env(App, Par, Val) || {Par, Val} <- Vals] + end, Conf).