diff --git a/include/emqx.hrl b/include/emqx.hrl index fba079b91..e52df41f6 100644 --- a/include/emqx.hrl +++ b/include/emqx.hrl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- -ifndef(EMQ_X_HRL). -define(EMQ_X_HRL, true). @@ -19,10 +21,6 @@ %% Banner %%-------------------------------------------------------------------- --define(COPYRIGHT, "Copyright (c) 2013-2019 EMQ Technologies Co., Ltd"). - --define(LICENSE_MESSAGE, "Licensed under the Apache License, Version 2.0"). - -define(PROTOCOL_VERSION, "MQTT/5.0"). -define(ERTS_MINIMUM_REQUIRED, "10.0"). @@ -47,8 +45,6 @@ %% Message and Delivery %%-------------------------------------------------------------------- --record(session, {sid, pid}). - -record(subscription, {topic, subid, subopts}). %% See 'Application Message' in MQTT Version 5.0 @@ -72,9 +68,12 @@ }). -record(delivery, { - sender :: pid(), %% Sender of the delivery - message :: #message{}, %% The message delivered - results :: list() %% Dispatches of the message + %% Sender of the delivery + sender :: pid(), + %% The message delivered + message :: #message{}, + %% Dispatches of the message + results :: list() }). %%-------------------------------------------------------------------- @@ -151,6 +150,7 @@ %%-------------------------------------------------------------------- %% Banned %%-------------------------------------------------------------------- + -type(banned_who() :: {client_id, binary()} | {username, binary()} | {ip_address, inet:ip_address()}). diff --git a/include/types.hrl b/include/types.hrl index 85a9aadf0..d8e96a20e 100644 --- a/include/types.hrl +++ b/include/types.hrl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- -type(maybe(T) :: undefined | T). diff --git a/src/emqx.erl b/src/emqx.erl index 3c7622887..4c04a6471 100644 --- a/src/emqx.erl +++ b/src/emqx.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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). @@ -61,9 +63,13 @@ -define(APP, ?MODULE). -%%------------------------------------------------------------------------------ +-define(COPYRIGHT, "Copyright (c) 2019 EMQ Technologies Co., Ltd"). + +-define(LICENSE_MESSAGE, "Licensed under the Apache License, Version 2.0"). + +%%-------------------------------------------------------------------- %% Bootstrap, is_running... -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Start emqx application -spec(start() -> {ok, list(atom())} | {error, term()}). @@ -95,9 +101,9 @@ is_running(Node) -> Pid when is_pid(Pid) -> true end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% PubSub API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(subscribe(emqx_topic:topic() | string()) -> ok). subscribe(Topic) -> @@ -122,9 +128,9 @@ publish(Msg) -> unsubscribe(Topic) -> emqx_broker:unsubscribe(iolist_to_binary(Topic)). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% PubSub management API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(topics() -> list(emqx_topic:topic())). topics() -> emqx_router:topics(). @@ -143,9 +149,9 @@ subscribed(SubPid, Topic) when is_pid(SubPid) -> subscribed(SubId, Topic) when is_atom(SubId); is_binary(SubId) -> emqx_broker:subscribed(SubId, iolist_to_binary(Topic)). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Hooks API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(hook(emqx_hooks:hookpoint(), emqx_hooks:action()) -> ok | {error, already_exists}). hook(HookPoint, Action) -> @@ -177,9 +183,9 @@ run_hook(HookPoint, Args) -> run_fold_hook(HookPoint, Args, Acc) -> emqx_hooks:run_fold(HookPoint, Args, Acc). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Shutdown and reboot -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- shutdown() -> shutdown(normal). @@ -193,12 +199,13 @@ 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). + diff --git a/src/emqx_access_control.erl b/src/emqx_access_control.erl index d6aab26d4..ae20c4489 100644 --- a/src/emqx_access_control.erl +++ b/src/emqx_access_control.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_access_control). @@ -22,9 +24,10 @@ , reload_acl/0 ]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- + -spec(authenticate(emqx_types:credentials()) -> {ok, emqx_types:credentials()} | {error, term()}). authenticate(Credentials) -> @@ -36,7 +39,8 @@ authenticate(Credentials) -> end. %% @doc Check ACL --spec(check_acl(emqx_types:credentials(), emqx_types:pubsub(), emqx_types:topic()) -> allow | deny). +-spec(check_acl(emqx_types:credentials(), emqx_types:pubsub(), emqx_types:topic()) + -> allow | deny). check_acl(Credentials, PubSub, Topic) -> case emqx_acl_cache:is_enabled() of false -> @@ -47,8 +51,7 @@ check_acl(Credentials, PubSub, Topic) -> AclResult = do_check_acl(Credentials, PubSub, Topic), emqx_acl_cache:put_acl_cache(PubSub, Topic, AclResult), AclResult; - AclResult -> - AclResult + AclResult -> AclResult end end. diff --git a/src/emqx_access_rule.erl b/src/emqx_access_rule.erl index 5d40341cc..2f57059cb 100644 --- a/src/emqx_access_rule.erl +++ b/src/emqx_access_rule.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_access_rule). @@ -38,9 +40,9 @@ -define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= deny))). -define(PUBSUB(A), ((A =:= subscribe) orelse (A =:= publish) orelse (A =:= pubsub))). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Compile Access Rule. compile({A, all}) when ?ALLOW_DENY(A) -> diff --git a/src/emqx_acl_cache.erl b/src/emqx_acl_cache.erl index 92d4d8328..b417f16fa 100644 --- a/src/emqx_acl_cache.erl +++ b/src/emqx_acl_cache.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_acl_cache). @@ -124,6 +126,7 @@ map_acl_cache(Fun) -> %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- + add_acl(PubSub, Topic, AclResult) -> K = cache_k(PubSub, Topic), V = cache_v(AclResult), diff --git a/src/emqx_alarm_handler.erl b/src/emqx_alarm_handler.erl index 51e112779..a12aa77a4 100644 --- a/src/emqx_alarm_handler.erl +++ b/src/emqx_alarm_handler.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_alarm_handler). @@ -46,9 +48,9 @@ -define(ALARM_TAB, emqx_alarm). -define(ALARM_HISTORY_TAB, emqx_alarm_history). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Mnesia bootstrap -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- mnesia(boot) -> ok = ekka_mnesia:create_table(?ALARM_TAB, [ @@ -63,13 +65,14 @@ mnesia(boot) -> {local_content, true}, {record_name, alarm_history}, {attributes, record_info(fields, alarm_history)}]); + mnesia(copy) -> ok = ekka_mnesia:copy_table(?ALARM_TAB), ok = ekka_mnesia:copy_table(?ALARM_HISTORY_TAB). -%%---------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% API -%%---------------------------------------------------------------------- +%%-------------------------------------------------------------------- load() -> gen_event:swap_handler(alarm_handler, {alarm_handler, swap}, {?MODULE, []}). @@ -81,13 +84,14 @@ unload() -> get_alarms() -> gen_event:call(alarm_handler, ?MODULE, get_alarms). -%%---------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% gen_event callbacks -%%---------------------------------------------------------------------- +%%-------------------------------------------------------------------- init({_Args, {alarm_handler, ExistingAlarms}}) -> init_tables(ExistingAlarms), {ok, []}; + init(_) -> init_tables([]), {ok, []}. @@ -133,9 +137,9 @@ init_tables(ExistingAlarms) -> set_alarm_history(Id) end, ExistingAlarms). -encode_alarm({AlarmId, #alarm{severity = Severity, +encode_alarm({AlarmId, #alarm{severity = Severity, title = Title, - summary = Summary, + summary = Summary, timestamp = Ts}}) -> emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}, {desc, [{severity, Severity}, @@ -143,7 +147,7 @@ encode_alarm({AlarmId, #alarm{severity = Severity, {summary, iolist_to_binary(Summary)}, {ts, emqx_time:now_secs(Ts)}]}]); encode_alarm({AlarmId, AlarmDesc}) -> - emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}, + emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}, {desc, maybe_to_binary(AlarmDesc)}]). alarm_msg(Topic, Payload) -> @@ -173,6 +177,6 @@ get_alarms_() -> [{Id, Desc} || #common_alarm{id = Id, desc = Desc} <- Alarms]. set_alarm_history(Id) -> - mnesia:dirty_write(?ALARM_HISTORY_TAB, #alarm_history{id = Id, - clear_at = undefined}). + His = #alarm_history{id = Id, clear_at = undefined}, + mnesia:dirty_write(?ALARM_HISTORY_TAB, His). diff --git a/src/emqx_app.erl b/src/emqx_app.erl index 8e8ce4c1b..57916a7fc 100644 --- a/src/emqx_app.erl +++ b/src/emqx_app.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_app). @@ -30,10 +32,10 @@ start(_Type, _Args) -> print_banner(), ekka:start(), {ok, Sup} = emqx_sup:start_link(), - emqx_modules:load(), - emqx_plugins:init(), + ok = emqx_modules:load(), + ok = emqx_plugins:init(), emqx_plugins:load(), - emqx_listeners:start(), + ok = emqx_listeners:start(), start_autocluster(), register(emqx, self()), diff --git a/src/emqx_banned.erl b/src/emqx_banned.erl index 11378461c..1cee38098 100644 --- a/src/emqx_banned.erl +++ b/src/emqx_banned.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_banned). @@ -44,14 +46,14 @@ , code_change/3 ]). --define(TAB, ?MODULE). +-define(BANNED_TAB, ?MODULE). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Mnesia bootstrap -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- mnesia(boot) -> - ok = ekka_mnesia:create_table(?TAB, [ + ok = ekka_mnesia:create_table(?BANNED_TAB, [ {type, set}, {disc_copies, [node()]}, {record_name, banned}, @@ -59,7 +61,7 @@ mnesia(boot) -> {storage_properties, [{ets, [{read_concurrency, true}]}]}]); mnesia(copy) -> - ok = ekka_mnesia:copy_table(?TAB). + ok = ekka_mnesia:copy_table(?BANNED_TAB). %% @doc Start the banned server. -spec(start_link() -> startlink_ret()). @@ -68,23 +70,23 @@ start_link() -> -spec(check(emqx_types:credentials()) -> boolean()). check(#{client_id := ClientId, username := Username, peername := {IPAddr, _}}) -> - ets:member(?TAB, {client_id, ClientId}) - orelse ets:member(?TAB, {username, Username}) - orelse ets:member(?TAB, {ipaddr, IPAddr}). + ets:member(?BANNED_TAB, {client_id, ClientId}) + orelse ets:member(?BANNED_TAB, {username, Username}) + orelse ets:member(?BANNED_TAB, {ipaddr, IPAddr}). -spec(add(emqx_types:banned()) -> ok). add(Banned) when is_record(Banned, banned) -> - mnesia:dirty_write(?TAB, Banned). + mnesia:dirty_write(?BANNED_TAB, Banned). -spec(delete({client_id, emqx_types:client_id()} | {username, emqx_types:username()} | {peername, emqx_types:peername()}) -> ok). delete(Key) -> - mnesia:dirty_delete(?TAB, Key). + mnesia:dirty_delete(?BANNED_TAB, Key). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> {ok, ensure_expiry_timer(#{expiry_timer => undefined})}. @@ -98,7 +100,8 @@ handle_cast(Msg, State) -> {noreply, State}. handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) -> - mnesia:async_dirty(fun expire_banned_items/1, [erlang:system_time(second)]), + mnesia:async_dirty(fun expire_banned_items/1, + [erlang:system_time(second)]), {noreply, ensure_expiry_timer(State), hibernate}; handle_info(Info, State) -> @@ -111,9 +114,9 @@ terminate(_Reason, #{expiry_timer := TRef}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -ifdef(TEST). ensure_expiry_timer(State) -> @@ -126,6 +129,7 @@ ensure_expiry_timer(State) -> expire_banned_items(Now) -> mnesia:foldl( fun(B = #banned{until = Until}, _Acc) when Until < Now -> - mnesia:delete_object(?TAB, B, sticky_write); + mnesia:delete_object(?BANNED_TAB, B, sticky_write); (_, _Acc) -> ok - end, ok, ?TAB). + end, ok, ?BANNED_TAB). + diff --git a/src/emqx_base62.erl b/src/emqx_base62.erl index 77d04fc4d..6d1f5a882 100644 --- a/src/emqx_base62.erl +++ b/src/emqx_base62.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_base62). @@ -21,9 +23,9 @@ , decode/2 ]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Encode any data to base62 binary -spec encode(string() | integer() | binary()) -> binary(). @@ -43,9 +45,9 @@ decode(L) when is_list(L) -> decode(B) when is_binary(B) -> decode(B, <<>>). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Interval Functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- encode(D, string) -> binary_to_list(encode(D)); diff --git a/src/emqx_batch.erl b/src/emqx_batch.erl index b7e341367..6d1df24ca 100644 --- a/src/emqx_batch.erl +++ b/src/emqx_batch.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_batch). @@ -22,18 +24,18 @@ , items/1 ]). --record(batch, - { batch_size :: non_neg_integer() - , batch_q :: list(any()) - , linger_ms :: pos_integer() - , linger_timer :: reference() | undefined - , commit_fun :: function() - }). +-record(batch, { + batch_size :: non_neg_integer(), + batch_q :: list(any()), + linger_ms :: pos_integer(), + linger_timer :: reference() | undefined, + commit_fun :: function() + }). --type(options() :: - #{ batch_size => non_neg_integer() - , linger_ms => pos_integer() - , commit_fun := function() +-type(options() :: #{ + batch_size => non_neg_integer(), + linger_ms => pos_integer(), + commit_fun := function() }). -opaque(batch() :: #batch{}). @@ -42,26 +44,31 @@ -export_type([batch/0]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(init(options()) -> batch()). init(Opts) when is_map(Opts) -> #batch{batch_size = maps:get(batch_size, Opts, 1000), batch_q = [], - linger_ms = maps:get(linger_ms, Opts, 1000), + linger_ms = maps:get(linger_ms, Opts, 1000), commit_fun = maps:get(commit_fun, Opts)}. -spec(push(any(), batch()) -> batch()). -push(El, Batch = #batch{batch_q = Q, linger_ms = Ms, linger_timer = undefined}) when length(Q) == 0 -> - Batch#batch{batch_q = [El], linger_timer = erlang:send_after(Ms, self(), batch_linger_expired)}; +push(El, Batch = #batch{batch_q = Q, + linger_ms = Ms, + linger_timer = undefined}) + when length(Q) == 0 -> + TRef = erlang:send_after(Ms, self(), batch_linger_expired), + Batch#batch{batch_q = [El], linger_timer = TRef}; %% no limit. push(El, Batch = #batch{batch_size = 0, batch_q = Q}) -> Batch#batch{batch_q = [El|Q]}; -push(El, Batch = #batch{batch_size = MaxSize, batch_q = Q}) when length(Q) >= MaxSize -> +push(El, Batch = #batch{batch_size = MaxSize, batch_q = Q}) + when length(Q) >= MaxSize -> commit(Batch#batch{batch_q = [El|Q]}); push(El, Batch = #batch{batch_q = Q}) -> diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index 5b5ab36bd..37791ac3a 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -1,3 +1,4 @@ +%%-------------------------------------------------------------------- %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,9 @@ %% 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. +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% @doc Bridge works in two layers (1) batching layer (2) transport layer %% The `bridge' batching layer collects local messages in batches and sends over %% to remote MQTT node/cluster via `connetion' transport layer. @@ -56,8 +59,10 @@ %% %% NOTES: %% * Local messages are all normalised to QoS-1 when exporting to remote +%%-------------------------------------------------------------------- -module(emqx_bridge). + -behaviour(gen_statem). %% APIs diff --git a/src/emqx_bridge_connect.erl b/src/emqx_bridge_connect.erl index 480129903..28fd585b9 100644 --- a/src/emqx_bridge_connect.erl +++ b/src/emqx_bridge_connect.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_bridge_connect). diff --git a/src/emqx_bridge_mqtt.erl b/src/emqx_bridge_mqtt.erl index 8a66f77a0..45c8cd47d 100644 --- a/src/emqx_bridge_mqtt.erl +++ b/src/emqx_bridge_mqtt.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,8 +12,10 @@ %% 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. +%%-------------------------------------------------------------------- -%% @doc This module implements EMQX Bridge transport layer on top of MQTT protocol +%% @doc This module implements EMQX Bridge transport layer on top of +%% MQTT protocol. -module(emqx_bridge_mqtt). diff --git a/src/emqx_bridge_msg.erl b/src/emqx_bridge_msg.erl index 6633027f9..8a70aeaa0 100644 --- a/src/emqx_bridge_msg.erl +++ b/src/emqx_bridge_msg.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_bridge_msg). diff --git a/src/emqx_bridge_rpc.erl b/src/emqx_bridge_rpc.erl index 9674fdcf1..9be3955bc 100644 --- a/src/emqx_bridge_rpc.erl +++ b/src/emqx_bridge_rpc.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,10 +12,12 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc This module implements EMQX Bridge transport layer based on gen_rpc. -module(emqx_bridge_rpc). + -behaviour(emqx_bridge_connect). %% behaviour callbacks diff --git a/src/emqx_bridge_sup.erl b/src/emqx_bridge_sup.erl index baf86fd70..1f62602a5 100644 --- a/src/emqx_bridge_sup.erl +++ b/src/emqx_bridge_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,8 +12,10 @@ %% 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_bridge_sup). + -behavior(supervisor). -include("logger.hrl"). diff --git a/src/emqx_broker.erl b/src/emqx_broker.erl index b9a4eb1bd..6c3b6163a 100644 --- a/src/emqx_broker.erl +++ b/src/emqx_broker.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_broker). diff --git a/src/emqx_broker_helper.erl b/src/emqx_broker_helper.erl index 9e9b337cf..34e2de817 100644 --- a/src/emqx_broker_helper.erl +++ b/src/emqx_broker_helper.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_broker_helper). @@ -92,9 +94,9 @@ create_seq(Topic) -> reclaim_seq(Topic) -> emqx_sequence:reclaim(?SUBSEQ, Topic). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> %% Helper table @@ -142,9 +144,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- clean_down(SubPid) -> case ets:lookup(?SUBMON, SubPid) of diff --git a/src/emqx_broker_sup.erl b/src/emqx_broker_sup.erl index 05154f393..325019ef6 100644 --- a/src/emqx_broker_sup.erl +++ b/src/emqx_broker_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_broker_sup). @@ -23,9 +25,9 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Supervisor callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> %% Broker pool @@ -34,20 +36,20 @@ init([]) -> {emqx_broker, start_link, []}]), %% Shared subscription - SharedSub = #{id => shared_sub, - start => {emqx_shared_sub, start_link, []}, - restart => permanent, + SharedSub = #{id => shared_sub, + start => {emqx_shared_sub, start_link, []}, + restart => permanent, shutdown => 2000, - type => worker, - modules => [emqx_shared_sub]}, + type => worker, + modules => [emqx_shared_sub]}, %% Broker helper - Helper = #{id => helper, - start => {emqx_broker_helper, start_link, []}, - restart => permanent, + Helper = #{id => helper, + start => {emqx_broker_helper, start_link, []}, + restart => permanent, shutdown => 2000, - type => worker, - modules => [emqx_broker_helper]}, + type => worker, + modules => [emqx_broker_helper]}, {ok, {{one_for_all, 0, 1}, [BrokerPool, SharedSub, Helper]}}. diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 2a16bb348..65612b0a2 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -32,7 +32,10 @@ , stats/1 ]). --export([kick/1]). +-export([ kick/1 + , discard/1 + , takeover/1 + ]). -export([session/1]). @@ -137,6 +140,12 @@ stats(#state{transport = Transport, kick(CPid) -> call(CPid, kick). +discard(CPid) -> + call(CPid, discard). + +takeover(CPid) -> + call(CPid, takeover). + session(CPid) -> call(CPid, session). @@ -286,6 +295,10 @@ handle({call, From}, kick, State) -> ok = gen_statem:reply(From, ok), shutdown(kicked, State); +handle({call, From}, discard, State) -> + ok = gen_statem:reply(From, ok), + shutdown(discard, State); + handle({call, From}, session, State = #state{proto_state = ProtoState}) -> reply(From, emqx_protocol:session(ProtoState), State); diff --git a/src/emqx_cli.erl b/src/emqx_cli.erl index e681936c3..3deb50b07 100644 --- a/src/emqx_cli.erl +++ b/src/emqx_cli.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_cli). @@ -35,3 +37,4 @@ usage(CmdList) -> usage(Format, Args) -> usage([{Format, Args}]). + diff --git a/src/emqx_client_sock.erl b/src/emqx_client_sock.erl index ae9b03305..f40064f17 100644 --- a/src/emqx_client_sock.erl +++ b/src/emqx_client_sock.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_client_sock). @@ -105,3 +107,4 @@ default_ciphers(TlsVersions) -> fun(TlsVer, Ciphers) -> Ciphers ++ ssl:cipher_suites(all, TlsVer) end, [], TlsVersions). + diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl index 852338854..2a42064d8 100644 --- a/src/emqx_cm.erl +++ b/src/emqx_cm.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,7 +12,9 @@ %% 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. +%%-------------------------------------------------------------------- +%% Channel Manager -module(emqx_cm). -behaviour(gen_server). @@ -24,25 +27,39 @@ -export([start_link/0]). --export([ register_connection/1 - , register_connection/2 - , unregister_connection/1 - , unregister_connection/2 +-export([ register_channel/1 + , unregister_channel/1 + , unregister_channel/2 ]). -export([ get_conn_attrs/1 , get_conn_attrs/2 , set_conn_attrs/2 - , set_conn_attrs/3 ]). -export([ get_conn_stats/1 , get_conn_stats/2 , set_conn_stats/2 - , set_conn_stats/3 ]). --export([lookup_conn_pid/1]). +-export([ open_session/1 + , discard_session/1 + , resume_session/1 + ]). + +-export([ get_session_attrs/1 + , get_session_attrs/2 + , set_session_attrs/2 + ]). + +-export([ get_session_stats/1 + , get_session_stats/2 + , set_session_stats/2 + ]). + +-export([ lookup_channels/1 + , lookup_channels/2 + ]). %% gen_server callbacks -export([ init/1 @@ -53,159 +70,350 @@ , code_change/3 ]). -%% internal export +%% Internal export -export([stats_fun/0]). --define(CM, ?MODULE). +-type(chan_pid() :: pid()). -%% ETS tables for connection management. --define(CONN_TAB, emqx_conn). --define(CONN_ATTRS_TAB, emqx_conn_attrs). --define(CONN_STATS_TAB, emqx_conn_stats). +-opaque(attrs() :: #{atom() => term()}). +-opaque(stats() :: #{atom() => integer()}). + +-export_type([attrs/0, stats/0]). + +%% Tables for channel management. +-define(CHAN_TAB, emqx_channel). + +-define(CONN_TAB, emqx_connection). + +-define(SESSION_TAB, emqx_session). + +-define(SESSION_P_TAB, emqx_session_p). + +%% Chan stats +-define(CHAN_STATS, + [{?CHAN_TAB, 'channels.count', 'channels.max'}, + {?CONN_TAB, 'connections.count', 'connections.max'}, + {?SESSION_TAB, 'sessions.count', 'sessions.max'}, + {?SESSION_P_TAB, 'sessions.persistent.count', 'sessions.persistent.max'} + ]). + +%% Batch drain -define(BATCH_SIZE, 100000). -%% @doc Start the connection manager. +%% Server name +-define(CM, ?MODULE). + +%% @doc Start the channel manager. -spec(start_link() -> startlink_ret()). start_link() -> gen_server:start_link({local, ?CM}, ?MODULE, [], []). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -%% @doc Register a connection. --spec(register_connection(emqx_types:client_id()) -> ok). -register_connection(ClientId) when is_binary(ClientId) -> - register_connection(ClientId, self()). +%% @doc Register a channel. +-spec(register_channel(emqx_types:client_id()) -> ok). +register_channel(ClientId) when is_binary(ClientId) -> + register_channel(ClientId, self()). --spec(register_connection(emqx_types:client_id(), pid()) -> ok). -register_connection(ClientId, ConnPid) when is_binary(ClientId), is_pid(ConnPid) -> - true = ets:insert(?CONN_TAB, {ClientId, ConnPid}), - notify({registered, ClientId, ConnPid}). +-spec(register_channel(emqx_types:client_id(), chan_pid()) -> ok). +register_channel(ClientId, ChanPid) -> + Chan = {ClientId, ChanPid}, + true = ets:insert(?CHAN_TAB, Chan), + ok = emqx_cm_registry:register_channel(Chan), + cast({registered, Chan}). -%% @doc Unregister a connection. --spec(unregister_connection(emqx_types:client_id()) -> ok). -unregister_connection(ClientId) when is_binary(ClientId) -> - unregister_connection(ClientId, self()). +%% @doc Unregister a channel. +-spec(unregister_channel(emqx_types:client_id()) -> ok). +unregister_channel(ClientId) when is_binary(ClientId) -> + unregister_channel(ClientId, self()). --spec(unregister_connection(emqx_types:client_id(), pid()) -> ok). -unregister_connection(ClientId, ConnPid) when is_binary(ClientId), is_pid(ConnPid) -> - true = do_unregister_connection({ClientId, ConnPid}), - notify({unregistered, ConnPid}). +-spec(unregister_channel(emqx_types:client_id(), chan_pid()) -> ok). +unregister_channel(ClientId, ChanPid) -> + Chan = {ClientId, ChanPid}, + true = do_unregister_channel(Chan), + cast({unregistered, Chan}). -do_unregister_connection(Conn) -> - true = ets:delete(?CONN_STATS_TAB, Conn), - true = ets:delete(?CONN_ATTRS_TAB, Conn), - true = ets:delete_object(?CONN_TAB, Conn). +%% @private +do_unregister_channel(Chan) -> + ok = emqx_cm_registry:unregister_channel(Chan), + true = ets:delete_object(?SESSION_P_TAB, Chan), + true = ets:delete(?SESSION_TAB, Chan), + true = ets:delete(?CONN_TAB, Chan), + ets:delete_object(?CHAN_TAB, Chan). -%% @doc Get conn attrs --spec(get_conn_attrs(emqx_types:client_id()) -> list()). -get_conn_attrs(ClientId) when is_binary(ClientId) -> - ConnPid = lookup_conn_pid(ClientId), - get_conn_attrs(ClientId, ConnPid). +%% @doc Get conn attrs. +-spec(get_conn_attrs(emqx_types:client_id()) -> maybe(attrs())). +get_conn_attrs(ClientId) -> + with_channel(ClientId, fun(ChanPid) -> + get_conn_attrs(ClientId, ChanPid) + end). --spec(get_conn_attrs(emqx_types:client_id(), pid()) -> list()). -get_conn_attrs(ClientId, ConnPid) when is_binary(ClientId) -> - emqx_tables:lookup_value(?CONN_ATTRS_TAB, {ClientId, ConnPid}, []). +-spec(get_conn_attrs(emqx_types:client_id(), chan_pid()) -> maybe(attrs())). +get_conn_attrs(ClientId, ChanPid) when node(ChanPid) == node() -> + Chan = {ClientId, ChanPid}, + try ets:lookup_element(?CONN_TAB, Chan, 2) of + Attrs -> Attrs + catch + error:badarg -> undefined + end; +get_conn_attrs(ClientId, ChanPid) -> + rpc_call(node(ChanPid), get_conn_attrs, [ClientId, ChanPid]). -%% @doc Set conn attrs --spec(set_conn_attrs(emqx_types:client_id(), list()) -> true). -set_conn_attrs(ClientId, Attrs) when is_binary(ClientId) -> - set_conn_attrs(ClientId, self(), Attrs). +%% @doc Set conn attrs. +-spec(set_conn_attrs(emqx_types:client_id(), attrs()) -> ok). +set_conn_attrs(ClientId, Attrs) when is_map(Attrs) -> + Chan = {ClientId, self()}, + case ets:update_element(?CONN_TAB, Chan, {2, Attrs}) of + true -> ok; + false -> true = ets:insert(?CONN_TAB, {Chan, Attrs, #{}}), + ok + end. --spec(set_conn_attrs(emqx_types:client_id(), pid(), list()) -> true). -set_conn_attrs(ClientId, ConnPid, Attrs) when is_binary(ClientId), is_pid(ConnPid) -> - Conn = {ClientId, ConnPid}, - ets:insert(?CONN_ATTRS_TAB, {Conn, Attrs}). +%% @doc Get conn stats. +-spec(get_conn_stats(emqx_types:client_id()) -> maybe(stats())). +get_conn_stats(ClientId) -> + with_channel(ClientId, fun(ChanPid) -> + get_conn_stats(ClientId, ChanPid) + end). -%% @doc Get conn stats --spec(get_conn_stats(emqx_types:client_id()) -> list(emqx_stats:stats())). -get_conn_stats(ClientId) when is_binary(ClientId) -> - ConnPid = lookup_conn_pid(ClientId), - get_conn_stats(ClientId, ConnPid). - --spec(get_conn_stats(emqx_types:client_id(), pid()) -> list(emqx_stats:stats())). -get_conn_stats(ClientId, ConnPid) when is_binary(ClientId) -> - Conn = {ClientId, ConnPid}, - emqx_tables:lookup_value(?CONN_STATS_TAB, Conn, []). +-spec(get_conn_stats(emqx_types:client_id(), chan_pid()) -> maybe(stats())). +get_conn_stats(ClientId, ChanPid) when node(ChanPid) == node() -> + Chan = {ClientId, ChanPid}, + try ets:lookup_element(?CONN_TAB, Chan, 3) of + Stats -> Stats + catch + error:badarg -> undefined + end; +get_conn_stats(ClientId, ChanPid) -> + rpc_call(node(ChanPid), get_conn_stats, [ClientId, ChanPid]). %% @doc Set conn stats. --spec(set_conn_stats(emqx_types:client_id(), list(emqx_stats:stats())) -> true). +-spec(set_conn_stats(emqx_types:client_id(), stats()) -> ok). set_conn_stats(ClientId, Stats) when is_binary(ClientId) -> set_conn_stats(ClientId, self(), Stats). --spec(set_conn_stats(emqx_types:client_id(), pid(), list(emqx_stats:stats())) -> true). -set_conn_stats(ClientId, ConnPid, Stats) when is_binary(ClientId), is_pid(ConnPid) -> - Conn = {ClientId, ConnPid}, - ets:insert(?CONN_STATS_TAB, {Conn, Stats}). +-spec(set_conn_stats(emqx_types:client_id(), chan_pid(), stats()) -> ok). +set_conn_stats(ClientId, ChanPid, Stats) -> + Chan = {ClientId, ChanPid}, + _ = ets:update_element(?CONN_TAB, Chan, {3, Stats}), + ok. -%% @doc Lookup connection pid. --spec(lookup_conn_pid(emqx_types:client_id()) -> maybe(pid())). -lookup_conn_pid(ClientId) when is_binary(ClientId) -> - emqx_tables:lookup_value(?CONN_TAB, ClientId). +%% @doc Open a session. +-spec(open_session(map()) -> {ok, emqx_session:session()} + | {error, Reason :: term()}). +open_session(Attrs = #{clean_start := true, + client_id := ClientId}) -> + CleanStart = fun(_) -> + ok = discard_session(ClientId), + {ok, emqx_session:new(Attrs)} + end, + emqx_cm_locker:trans(ClientId, CleanStart); -notify(Msg) -> - gen_server:cast(?CM, {notify, Msg}). +open_session(Attrs = #{clean_start := false, + client_id := ClientId}) -> + ResumeStart = fun(_) -> + case resume_session(ClientId) of + {ok, Session} -> + {ok, Session, true}; + {error, not_found} -> + {ok, emqx_session:new(Attrs)} + end + end, + emqx_cm_locker:trans(ClientId, ResumeStart). -%%----------------------------------------------------------------------------- +%% @doc Try to resume a session. +-spec(resume_session(emqx_types:client_id()) + -> {ok, emqx_session:session()} | {error, Reason :: term()}). +resume_session(ClientId) -> + case lookup_channels(ClientId) of + [] -> {error, not_found}; + [ChanPid] -> + emqx_channel:resume(ChanPid); + ChanPids -> + [ChanPid|StalePids] = lists:reverse(ChanPids), + ?LOG(error, "[SM] More than one channel found: ~p", [ChanPids]), + lists:foreach(fun(StalePid) -> + catch emqx_channel:discard(StalePid) + end, StalePids), + emqx_channel:resume(ChanPid) + end. + +%% @doc Discard all the sessions identified by the ClientId. +-spec(discard_session(emqx_types:client_id()) -> ok). +discard_session(ClientId) when is_binary(ClientId) -> + case lookup_channels(ClientId) of + [] -> ok; + ChanPids -> + lists:foreach( + fun(ChanPid) -> + try emqx_channel:discard(ChanPid) + catch + _:Error:_Stk -> + ?LOG(warning, "[SM] Failed to discard ~p: ~p", [ChanPid, Error]) + end + end, ChanPids) + end. + +%% @doc Get session attrs. +-spec(get_session_attrs(emqx_types:client_id()) -> attrs()). +get_session_attrs(ClientId) -> + with_channel(ClientId, fun(ChanPid) -> + get_session_attrs(ClientId, ChanPid) + end). + +-spec(get_session_attrs(emqx_types:client_id(), chan_pid()) -> maybe(attrs())). +get_session_attrs(ClientId, ChanPid) when node(ChanPid) == node() -> + Chan = {ClientId, ChanPid}, + try ets:lookup_element(?SESSION_TAB, Chan, 2) of + Attrs -> Attrs + catch + error:badarg -> undefined + end; +get_session_attrs(ClientId, ChanPid) -> + rpc_call(node(ChanPid), get_session_attrs, [ClientId, ChanPid]). + +%% @doc Set session attrs. +-spec(set_session_attrs(emqx_types:client_id(), attrs()) -> ok). +set_session_attrs(ClientId, Attrs) when is_binary(ClientId) -> + Chan = {ClientId, self()}, + case ets:update_element(?SESSION_TAB, Chan, {2, Attrs}) of + true -> ok; + false -> + true = ets:insert(?SESSION_TAB, {Chan, Attrs, #{}}), + is_clean_start(Attrs) orelse ets:insert(?SESSION_P_TAB, Chan), + ok + end. + +%% @doc Is clean start? +is_clean_start(#{clean_start := false}) -> false; +is_clean_start(_Attrs) -> true. + +%% @doc Get session stats. +-spec(get_session_stats(emqx_types:client_id()) -> stats()). +get_session_stats(ClientId) -> + with_channel(ClientId, fun(ChanPid) -> + get_session_stats(ClientId, ChanPid) + end). + +-spec(get_session_stats(emqx_types:client_id(), chan_pid()) -> maybe(stats())). +get_session_stats(ClientId, ChanPid) when node(ChanPid) == node() -> + Chan = {ClientId, ChanPid}, + try ets:lookup_element(?SESSION_TAB, Chan, 3) of + Stats -> Stats + catch + error:badarg -> undefined + end; +get_session_stats(ClientId, ChanPid) -> + rpc_call(node(ChanPid), get_session_stats, [ClientId, ChanPid]). + +%% @doc Set session stats. +-spec(set_session_stats(emqx_types:client_id(), stats()) -> ok). +set_session_stats(ClientId, Stats) when is_binary(ClientId) -> + set_session_stats(ClientId, self(), Stats). + +-spec(set_session_stats(emqx_types:client_id(), chan_pid(), stats()) -> ok). +set_session_stats(ClientId, ChanPid, Stats) -> + Chan = {ClientId, ChanPid}, + _ = ets:update_element(?SESSION_TAB, Chan, {3, Stats}), + ok. + +with_channel(ClientId, Fun) -> + case lookup_channels(ClientId) of + [] -> undefined; + [Pid] -> Fun(Pid); + Pids -> Fun(lists:last(Pids)) + end. + +%% @doc Lookup channels. +-spec(lookup_channels(emqx_types:client_id()) -> list(chan_pid())). +lookup_channels(ClientId) -> + lookup_channels(global, ClientId). + +%% @doc Lookup local or global channels. +-spec(lookup_channels(local | global, emqx_types:client_id()) -> list(chan_pid())). +lookup_channels(global, ClientId) -> + case emqx_cm_registry:is_enabled() of + true -> + emqx_cm_registry:lookup_channels(ClientId); + false -> + lookup_channels(local, ClientId) + end; + +lookup_channels(local, ClientId) -> + [ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)]. + +%% @private +rpc_call(Node, Fun, Args) -> + case rpc:call(Node, ?MODULE, Fun, Args) of + {badrpc, Reason} -> error(Reason); + Res -> Res + end. + +%% @private +cast(Msg) -> gen_server:cast(?CM, Msg). + +%%-------------------------------------------------------------------- %% gen_server callbacks -%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- init([]) -> - TabOpts = [public, set, {write_concurrency, true}], - ok = emqx_tables:new(?CONN_TAB, [{read_concurrency, true} | TabOpts]), - ok = emqx_tables:new(?CONN_ATTRS_TAB, TabOpts), - ok = emqx_tables:new(?CONN_STATS_TAB, TabOpts), - ok = emqx_stats:update_interval(conn_stats, fun ?MODULE:stats_fun/0), - {ok, #{conn_pmon => emqx_pmon:new()}}. + TabOpts = [public, {write_concurrency, true}], + ok = emqx_tables:new(?CHAN_TAB, [bag, {read_concurrency, true} | TabOpts]), + ok = emqx_tables:new(?CONN_TAB, [set, compressed | TabOpts]), + ok = emqx_tables:new(?SESSION_TAB, [set, compressed | TabOpts]), + ok = emqx_tables:new(?SESSION_P_TAB, [bag | TabOpts]), + ok = emqx_stats:update_interval(chan_stats, fun ?MODULE:stats_fun/0), + {ok, #{chan_pmon => emqx_pmon:new()}}. handle_call(Req, _From, State) -> ?LOG(error, "Unexpected call: ~p", [Req]), {reply, ignored, State}. -handle_cast({notify, {registered, ClientId, ConnPid}}, State = #{conn_pmon := PMon}) -> - {noreply, State#{conn_pmon := emqx_pmon:monitor(ConnPid, ClientId, PMon)}}; +handle_cast({registered, {ClientId, ChanPid}}, State = #{chan_pmon := PMon}) -> + PMon1 = emqx_pmon:monitor(ChanPid, ClientId, PMon), + {noreply, State#{chan_pmon := PMon1}}; -handle_cast({notify, {unregistered, ConnPid}}, State = #{conn_pmon := PMon}) -> - {noreply, State#{conn_pmon := emqx_pmon:demonitor(ConnPid, PMon)}}; +handle_cast({unregistered, {_ClientId, ChanPid}}, State = #{chan_pmon := PMon}) -> + PMon1 = emqx_pmon:demonitor(ChanPid, PMon), + {noreply, State#{chan_pmon := PMon1}}; handle_cast(Msg, State) -> ?LOG(error, "Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{conn_pmon := PMon}) -> - ConnPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)], - {Items, PMon1} = emqx_pmon:erase_all(ConnPids, PMon), - ok = emqx_pool:async_submit( - fun lists:foreach/2, [fun clean_down/1, Items]), - {noreply, State#{conn_pmon := PMon1}}; +handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) -> + ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)], + {Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon), + ok = emqx_pool:async_submit(fun lists:foreach/2, [fun clean_down/1, Items]), + {noreply, State#{chan_pmon := PMon1}}; handle_info(Info, State) -> ?LOG(error, "Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> - emqx_stats:cancel_update(conn_stats). + emqx_stats:cancel_update(chan_stats). code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -clean_down({Pid, ClientId}) -> - Conn = {ClientId, Pid}, - case ets:member(?CONN_TAB, ClientId) - orelse ets:member(?CONN_ATTRS_TAB, Conn) of - true -> - do_unregister_connection(Conn); - false -> false - end. +clean_down({ChanPid, ClientId}) -> + Chan = {ClientId, ChanPid}, + do_unregister_channel(Chan). stats_fun() -> - case ets:info(?CONN_TAB, size) of + lists:foreach(fun update_stats/1, ?CHAN_STATS). + +update_stats({Tab, Stat, MaxStat}) -> + case ets:info(Tab, size) of undefined -> ok; - Size -> emqx_stats:setstat('connections.count', 'connections.max', Size) + Size -> emqx_stats:setstat(Stat, MaxStat, Size) end. + diff --git a/src/emqx_sm_locker.erl b/src/emqx_cm_locker.erl similarity index 93% rename from src/emqx_sm_locker.erl rename to src/emqx_cm_locker.erl index 854b95653..c5cd9c2f6 100644 --- a/src/emqx_sm_locker.erl +++ b/src/emqx_cm_locker.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,8 +12,9 @@ %% 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_sm_locker). +-module(emqx_cm_locker). -include("emqx.hrl"). -include("types.hrl"). @@ -26,10 +28,6 @@ , unlock/1 ]). -%%------------------------------------------------------------------------------ -%% APIs -%%------------------------------------------------------------------------------ - -spec(start_link() -> startlink_ret()). start_link() -> ekka_locker:start_link(?MODULE). diff --git a/src/emqx_sm_registry.erl b/src/emqx_cm_registry.erl similarity index 56% rename from src/emqx_sm_registry.erl rename to src/emqx_cm_registry.erl index 5e8d4663d..ce843a427 100644 --- a/src/emqx_sm_registry.erl +++ b/src/emqx_cm_registry.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,8 +12,10 @@ %% 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_sm_registry). +%% Global Channel Registry +-module(emqx_cm_registry). -behaviour(gen_server). @@ -24,12 +27,14 @@ -export([start_link/0]). --export([ is_enabled/0 - , register_session/1 - , lookup_session/1 - , unregister_session/1 +-export([is_enabled/0]). + +-export([ register_channel/1 + , unregister_channel/1 ]). +-export([lookup_channels/1]). + %% gen_server callbacks -export([ init/1 , handle_call/3 @@ -40,57 +45,67 @@ ]). -define(REGISTRY, ?MODULE). --define(TAB, emqx_session_registry). --define(LOCK, {?MODULE, cleanup_sessions}). +-define(TAB, emqx_channel_registry). +-define(LOCK, {?MODULE, cleanup_down}). --record(global_session, {sid, pid}). +-record(channel, {chid, pid}). --type(session_pid() :: pid()). - -%%------------------------------------------------------------------------------ -%% APIs -%%------------------------------------------------------------------------------ - -%% @doc Start the global session manager. +%% @doc Start the global channel registry. -spec(start_link() -> startlink_ret()). start_link() -> gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []). +%%-------------------------------------------------------------------- +%% APIs +%%-------------------------------------------------------------------- + +%% @doc Is the global registry enabled? -spec(is_enabled() -> boolean()). is_enabled() -> - emqx_config:get_env(enable_session_registry, true). + emqx_config:get_env(enable_channel_registry, true). --spec(lookup_session(emqx_types:client_id()) -> list(session_pid())). -lookup_session(ClientId) -> - [SessPid || #global_session{pid = SessPid} <- mnesia:dirty_read(?TAB, ClientId)]. +%% @doc Register a global channel. +-spec(register_channel(emqx_types:client_id() + | {emqx_types:client_id(), pid()}) -> ok). +register_channel(ClientId) when is_binary(ClientId) -> + register_channel({ClientId, self()}); --spec(register_session({emqx_types:client_id(), session_pid()}) -> ok). -register_session({ClientId, SessPid}) when is_binary(ClientId), is_pid(SessPid) -> +register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) -> case is_enabled() of - true -> mnesia:dirty_write(?TAB, record(ClientId, SessPid)); + true -> mnesia:dirty_write(?TAB, record(ClientId, ChanPid)); false -> ok end. --spec(unregister_session({emqx_types:client_id(), session_pid()}) -> ok). -unregister_session({ClientId, SessPid}) when is_binary(ClientId), is_pid(SessPid) -> +%% @doc Unregister a global channel. +-spec(unregister_channel(emqx_types:client_id() + | {emqx_types:client_id(), pid()}) -> ok). +unregister_channel(ClientId) when is_binary(ClientId) -> + unregister_channel({ClientId, self()}); + +unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) -> case is_enabled() of - true -> mnesia:dirty_delete_object(?TAB, record(ClientId, SessPid)); + true -> mnesia:dirty_delete_object(?TAB, record(ClientId, ChanPid)); false -> ok end. -record(ClientId, SessPid) -> - #global_session{sid = ClientId, pid = SessPid}. +%% @doc Lookup the global channels. +-spec(lookup_channels(emqx_types:client_id()) -> list(pid())). +lookup_channels(ClientId) -> + [ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)]. -%%------------------------------------------------------------------------------ +record(ClientId, ChanPid) -> + #channel{chid = ClientId, pid = ChanPid}. + +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> ok = ekka_mnesia:create_table(?TAB, [ {type, bag}, {ram_copies, [node()]}, - {record_name, global_session}, - {attributes, record_info(fields, global_session)}, + {record_name, channel}, + {attributes, record_info(fields, channel)}, {storage_properties, [{ets, [{read_concurrency, true}, {write_concurrency, true}]}]}]), ok = ekka_mnesia:copy_table(?TAB), @@ -108,7 +123,7 @@ handle_cast(Msg, State) -> handle_info({membership, {mnesia, down, Node}}, State) -> global:trans({?LOCK, self()}, fun() -> - mnesia:transaction(fun cleanup_sessions/1, [Node]) + mnesia:transaction(fun cleanup_channels/1, [Node]) end), {noreply, State}; @@ -125,14 +140,14 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -cleanup_sessions(Node) -> - Pat = [{#global_session{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}], - lists:foreach(fun delete_session/1, mnesia:select(?TAB, Pat, write)). +cleanup_channels(Node) -> + Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}], + lists:foreach(fun delete_channel/1, mnesia:select(?TAB, Pat, write)). -delete_session(Session) -> - mnesia:delete_object(?TAB, Session, write). +delete_channel(Chan) -> + mnesia:delete_object(?TAB, Chan, write). diff --git a/src/emqx_cm_sup.erl b/src/emqx_cm_sup.erl index 19940da05..6cbf8d432 100644 --- a/src/emqx_cm_sup.erl +++ b/src/emqx_cm_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_cm_sup). @@ -30,20 +32,39 @@ init([]) -> shutdown => 1000, type => worker, modules => [emqx_banned]}, - FlappingOption = emqx_config:get_env(flapping_clean_interval, 3600000), Flapping = #{id => flapping, - start => {emqx_flapping, start_link, [FlappingOption]}, + start => {emqx_flapping, start_link, []}, restart => permanent, shutdown => 1000, type => worker, modules => [emqx_flapping]}, + %% Channel locker + Locker = #{id => locker, + start => {emqx_cm_locker, start_link, []}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [emqx_cm_locker] + }, + %% Channel registry + Registry = #{id => registry, + start => {emqx_cm_registry, start_link, []}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [emqx_cm_registry] + }, + %% Channel Manager Manager = #{id => manager, start => {emqx_cm, start_link, []}, restart => permanent, - shutdown => 2000, + shutdown => 5000, type => worker, - modules => [emqx_cm]}, + modules => [emqx_cm] + }, SupFlags = #{strategy => one_for_one, intensity => 100, - period => 10}, - {ok, {SupFlags, [Banned, Manager, Flapping]}}. + period => 10 + }, + {ok, {SupFlags, [Banned, Flapping, Locker, Registry, Manager]}}. + diff --git a/src/emqx_config.erl b/src/emqx_config.erl index fd80de0c2..3d4c33369 100644 --- a/src/emqx_config.erl +++ b/src/emqx_config.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc Hot Configuration %% diff --git a/src/emqx_ctl.erl b/src/emqx_ctl.erl index 09bab3e58..08b9a56fd 100644 --- a/src/emqx_ctl.erl +++ b/src/emqx_ctl.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_ctl). diff --git a/src/emqx_flapping.erl b/src/emqx_flapping.erl index 099bf3910..94e1bcc78 100644 --- a/src/emqx_flapping.erl +++ b/src/emqx_flapping.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,9 @@ %% 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. +%%-------------------------------------------------------------------- + +%% @doc This module is used to garbage clean the flapping records. -module(emqx_flapping). @@ -19,31 +23,29 @@ -behaviour(gen_statem). --export([start_link/1]). - -%% This module is used to garbage clean the flapping records +-export([start_link/0]). %% gen_statem callbacks --export([ terminate/3 - , code_change/4 - , init/1 +-export([ init/1 , initialized/3 , callback_mode/0 + , terminate/3 + , code_change/4 ]). -define(FLAPPING_TAB, ?MODULE). -export([check/3]). --record(flapping, - { client_id :: binary() - , check_count :: integer() - , timestamp :: integer() - }). +-record(flapping, { + client_id :: binary(), + check_count :: integer(), + timestamp :: integer() + }). -type(flapping_record() :: #flapping{}). --type(flapping_state() :: flapping | ok). +-type(flapping_state() :: flapping | ok). %% @doc This function is used to initialize flapping records %% the expiry time unit is minutes. @@ -96,18 +98,20 @@ check_flapping(Action, CheckCount, _Threshold = {TimesThreshold, TimeInterval}, %%-------------------------------------------------------------------- %% gen_statem callbacks %%-------------------------------------------------------------------- --spec(start_link(TimerInterval :: [integer()]) -> startlink_ret()). -start_link(TimerInterval) -> - gen_statem:start_link({local, ?MODULE}, ?MODULE, [TimerInterval], []). -init([TimerInterval]) -> +-spec(start_link() -> startlink_ret()). +start_link() -> + gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + Interval = emqx_config:get_env(flapping_clean_interval, 3600000), TabOpts = [ public , set , {keypos, 2} , {write_concurrency, true} , {read_concurrency, true}], ok = emqx_tables:new(?FLAPPING_TAB, TabOpts), - {ok, initialized, #{timer_interval => TimerInterval}}. + {ok, initialized, #{timer_interval => Interval}}. callback_mode() -> [state_functions, state_enter]. @@ -134,3 +138,4 @@ clean_expired_records() -> NowTime = emqx_time:now_secs(), MatchSpec = [{{'$1', '$2', '$3'},[{'<', '$3', NowTime}], [true]}], ets:select_delete(?FLAPPING_TAB, MatchSpec). + diff --git a/src/emqx_gc.erl b/src/emqx_gc.erl index fa0d3d6ed..256a5c4ff 100644 --- a/src/emqx_gc.erl +++ b/src/emqx_gc.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,9 +12,10 @@ %% 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. +%%-------------------------------------------------------------------- -%% @doc This module manages an opaque collection of statistics data used to -%% force garbage collection on `self()' process when hitting thresholds. +%% @doc This module manages an opaque collection of statistics data used +%% to force garbage collection on `self()' process when hitting thresholds. %% Namely: %% (1) Total number of messages passed through %% (2) Total data volume passed through @@ -85,9 +87,9 @@ reset(?GCS(St)) -> reset(undefined) -> undefined. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(dec(cnt | oct, pos_integer(), st()) -> {boolean(), st()}). dec(Key, Num, St) -> diff --git a/src/emqx_gen_mod.erl b/src/emqx_gen_mod.erl index dc9304f98..9ae4d5b6f 100644 --- a/src/emqx_gen_mod.erl +++ b/src/emqx_gen_mod.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_gen_mod). diff --git a/src/emqx_guid.erl b/src/emqx_guid.erl index 2e54e9aaa..2a3715b9e 100644 --- a/src/emqx_guid.erl +++ b/src/emqx_guid.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc Generate global unique id for mqtt message. %% diff --git a/src/emqx_hooks.erl b/src/emqx_hooks.erl index 594aacbec..220383ebb 100644 --- a/src/emqx_hooks.erl +++ b/src/emqx_hooks.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_hooks). @@ -21,7 +23,9 @@ -logger_header("[Hooks]"). --export([start_link/0, stop/0]). +-export([ start_link/0 + , stop/0 + ]). %% Hooks API -export([ add/2 @@ -54,11 +58,16 @@ -type(action() :: function() | mfa()). -type(filter() :: function() | mfa()). --record(callback, {action :: action(), - filter :: filter(), - priority :: integer()}). +-record(callback, { + action :: action(), + filter :: filter(), + priority :: integer() + }). --record(hook, {name :: hookpoint(), callbacks :: list(#callback{})}). +-record(hook, { + name :: hookpoint(), + callbacks :: list(#callback{}) + }). -export_type([hookpoint/0, action/0, filter/0]). @@ -67,15 +76,16 @@ -spec(start_link() -> startlink_ret()). start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], [{hibernate_after, 1000}]). + gen_server:start_link({local, ?SERVER}, + ?MODULE, [], [{hibernate_after, 1000}]). -spec(stop() -> ok). stop() -> gen_server:stop(?SERVER, normal, infinity). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Hooks API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Register a callback -spec(add(hookpoint(), action() | #callback{}) -> ok_or_error(already_exists)). @@ -113,7 +123,6 @@ run(HookPoint, Args) -> run_fold(HookPoint, Args, Acc) -> do_run_fold(lookup(HookPoint), Args, Acc). - do_run([#callback{action = Action, filter = Filter} | Callbacks], Args) -> case filter_passed(Filter, Args) andalso execute(Action, Args) of %% stop the hook chain and return @@ -165,12 +174,12 @@ lookup(HookPoint) -> [] -> [] end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> - ok = emqx_tables:new(?TAB, [{keypos, #hook.name}, {read_concurrency, true}, protected]), + ok = emqx_tables:new(?TAB, [{keypos, #hook.name}, {read_concurrency, true}]), {ok, #{}}. handle_call({add, HookPoint, Callback = #callback{action = Action}}, _From, State) -> diff --git a/src/emqx_inflight.erl b/src/emqx_inflight.erl index 7a41e4ec7..4522b3510 100644 --- a/src/emqx_inflight.erl +++ b/src/emqx_inflight.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_inflight). @@ -38,17 +40,18 @@ -opaque(inflight() :: {?MODULE, max_size(), gb_trees:tree()}). -define(Inflight(Tree), {?MODULE, _MaxSize, Tree}). + -define(Inflight(MaxSize, Tree), {?MODULE, MaxSize, (Tree)}). -export_type([inflight/0]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(new(non_neg_integer()) -> inflight()). new(MaxSize) when MaxSize >= 0 -> - {?MODULE, MaxSize, gb_trees:empty()}. + ?Inflight(MaxSize, gb_trees:empty()). -spec(contain(key(), inflight()) -> boolean()). contain(Key, ?Inflight(Tree)) -> diff --git a/src/emqx_json.erl b/src/emqx_json.erl index 07a2e9a23..c9130646f 100644 --- a/src/emqx_json.erl +++ b/src/emqx_json.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_json). @@ -35,12 +37,12 @@ encode(Term, Opts) -> jsx:encode(Term, Opts). -spec(safe_encode(jsx:json_term()) - -> {ok, jsx:json_text()} | {error, term()}). + -> {ok, jsx:json_text()} | {error, Reason :: term()}). safe_encode(Term) -> safe_encode(Term, []). -spec(safe_encode(jsx:json_term(), jsx_to_json:config()) - -> {ok, jsx:json_text()} | {error, term()}). + -> {ok, jsx:json_text()} | {error, Reason :: term()}). safe_encode(Term, Opts) -> try encode(Term, Opts) of Json -> {ok, Json} @@ -58,12 +60,12 @@ decode(Json, Opts) -> jsx:decode(Json, Opts). -spec(safe_decode(jsx:json_text()) - -> {ok, jsx:json_term()} | {error, term()}). + -> {ok, jsx:json_term()} | {error, Reason :: term()}). safe_decode(Json) -> safe_decode(Json, []). -spec(safe_decode(jsx:json_text(), jsx_to_json:config()) - -> {ok, jsx:json_term()} | {error, term()}). + -> {ok, jsx:json_term()} | {error, Reason :: term()}). safe_decode(Json, Opts) -> try decode(Json, Opts) of Term -> {ok, Term} diff --git a/src/emqx_keepalive.erl b/src/emqx_keepalive.erl index ebd5e18d4..36cb6b335 100644 --- a/src/emqx_keepalive.erl +++ b/src/emqx_keepalive.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_keepalive). @@ -20,15 +22,22 @@ , cancel/1 ]). --record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}). +-record(keepalive, { + statfun, + statval, + tsec, + tmsg, + tref, + repeat = 0 + }). -opaque(keepalive() :: #keepalive{}). -export_type([keepalive/0]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Start a keepalive -spec(start(fun(), integer(), any()) -> {ok, keepalive()} | {error, term()}). @@ -79,3 +88,4 @@ cancel(_) -> timer(Secs, Msg) -> erlang:send_after(timer:seconds(Secs), self(), Msg). + diff --git a/src/emqx_kernel_sup.erl b/src/emqx_kernel_sup.erl index cfed226cd..486942b53 100644 --- a/src/emqx_kernel_sup.erl +++ b/src/emqx_kernel_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_kernel_sup). diff --git a/src/emqx_listeners.erl b/src/emqx_listeners.erl index 745daf000..043b55fcc 100644 --- a/src/emqx_listeners.erl +++ b/src/emqx_listeners.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc Start/Stop MQTT listeners. -module(emqx_listeners). @@ -33,9 +35,9 @@ -type(listener() :: {esockd:proto(), esockd:listen_on(), [esockd:option()]}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Start all listeners. -spec(start() -> ok). @@ -167,3 +169,4 @@ format({Addr, Port}) when is_list(Addr) -> io_lib:format("~s:~w", [Addr, Port]); format({Addr, Port}) when is_tuple(Addr) -> io_lib:format("~s:~w", [esockd_net:ntoab(Addr), Port]). + diff --git a/src/emqx_logger.erl b/src/emqx_logger.erl index 7f45843e3..91905a35e 100644 --- a/src/emqx_logger.erl +++ b/src/emqx_logger.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,10 +12,11 @@ %% 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_logger). --compile({no_auto_import,[error/1]}). +-compile({no_auto_import, [error/1]}). %% Logs -export([ debug/1 @@ -50,9 +52,9 @@ -export([parse_transform/2]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- debug(Msg) -> logger:debug(Msg). @@ -125,9 +127,9 @@ set_log_level(Level) -> parse_transform(AST, _Opts) -> trans(AST, "", []). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal Functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- log_hanlder_info(#{id := Id, level := Level, module := logger_std_h, config := #{type := Type}}) when Type =:= standard_io; diff --git a/src/emqx_logger_formatter.erl b/src/emqx_logger_formatter.erl index 92c194883..6cfe1ca58 100644 --- a/src/emqx_logger_formatter.erl +++ b/src/emqx_logger_formatter.erl @@ -34,18 +34,22 @@ -define(IS_STRING(String), (is_list(String) orelse is_binary(String))). -%%%----------------------------------------------------------------- -%%% Types --type config() :: #{chars_limit => pos_integer() | unlimited, - depth => pos_integer() | unlimited, - max_size => pos_integer() | unlimited, - report_cb => logger:report_cb(), - quit => template()}. --type template() :: [metakey() | {metakey(),template(),template()} | string()]. --type metakey() :: atom() | [atom()]. +%%-------------------------------------------------------------------- +%% Types + +-type(config() :: #{chars_limit => pos_integer() | unlimited, + depth => pos_integer() | unlimited, + max_size => pos_integer() | unlimited, + report_cb => logger:report_cb(), + quit => template()}). + +-type(template() :: [metakey() | {metakey(),template(),template()} | string()]). + +-type(metakey() :: atom() | [atom()]). + +%%-------------------------------------------------------------------- +%% API -%%%----------------------------------------------------------------- -%%% API -spec format(LogEvent,Config) -> unicode:chardata() when LogEvent :: logger:log_event(), Config :: config(). diff --git a/src/emqx_logger_handler.erl b/src/emqx_logger_handler.erl index e0f5d9af4..c6492447a 100644 --- a/src/emqx_logger_handler.erl +++ b/src/emqx_logger_handler.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_logger_handler). @@ -19,8 +21,8 @@ -export([init/0]). init() -> - logger:add_handler(emqx_logger_handler, - emqx_logger_handler, + logger:add_handler(emqx_logger_handler, + emqx_logger_handler, #{level => error, filters => [{easy_filter, {fun filter_by_level/2, []}}], filters_default => stop}). @@ -41,3 +43,4 @@ filter_by_level(LogEvent = #{level := error}, _Extra) -> LogEvent; filter_by_level(_LogEvent, _Extra) -> stop. + diff --git a/src/emqx_message.erl b/src/emqx_message.erl index 23ddd69d4..a00928af8 100644 --- a/src/emqx_message.erl +++ b/src/emqx_message.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_message). diff --git a/src/emqx_metrics.erl b/src/emqx_metrics.erl index fdf66a493..7a40eaf4e 100644 --- a/src/emqx_metrics.erl +++ b/src/emqx_metrics.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_metrics). @@ -145,9 +147,9 @@ start_link() -> stop() -> gen_server:stop(?SERVER). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Metrics API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(new(metric_name()) -> ok). new(Name) -> @@ -251,9 +253,9 @@ update_counter(Name, Value) -> end, counters:add(CRef, CIdx, Value). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Inc Received/Sent metrics -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Inc packets received. -spec(inc_recv(emqx_mqtt_types:packet()) -> ok). @@ -339,9 +341,9 @@ do_inc_sent(?PACKET(?AUTH)) -> do_inc_sent(_Packet) -> ignore. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> % Create counters array @@ -391,9 +393,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- reserved_idx('bytes.received') -> 01; reserved_idx('bytes.sent') -> 02; diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index e236b560f..26c97c92f 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_misc). diff --git a/src/emqx_mod_acl_internal.erl b/src/emqx_mod_acl_internal.erl index 9c88c9ea2..8eaa6e138 100644 --- a/src/emqx_mod_acl_internal.erl +++ b/src/emqx_mod_acl_internal.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_mod_acl_internal). @@ -37,9 +39,9 @@ -type(acl_rules() :: #{publish => [emqx_access_rule:rule()], subscribe => [emqx_access_rule:rule()]}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- load(_Env) -> Rules = rules_from_file(acl_file()), @@ -54,9 +56,9 @@ unload(_Env) -> all_rules() -> rules_from_file(acl_file()). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% ACL callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Check ACL -spec(check_acl(emqx_types:credentials(), emqx_types:pubsub(), emqx_topic:topic(), @@ -73,9 +75,9 @@ check_acl(Credentials, PubSub, Topic, _AclResult, Rules) -> reload_acl() -> unload([]), load([]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal Functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- acl_file() -> emqx_config:get_env(acl_file). diff --git a/src/emqx_mod_presence.erl b/src/emqx_mod_presence.erl index cab5076f5..febe4380a 100644 --- a/src/emqx_mod_presence.erl +++ b/src/emqx_mod_presence.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_mod_presence). @@ -33,9 +35,9 @@ -define(ATTR_KEYS, [clean_start, proto_ver, proto_name, keepalive]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- load(Env) -> emqx_hooks:add('client.connected', fun ?MODULE:on_client_connected/4, [Env]), diff --git a/src/emqx_mod_rewrite.erl b/src/emqx_mod_rewrite.erl index b06428e9f..0b0285ddf 100644 --- a/src/emqx_mod_rewrite.erl +++ b/src/emqx_mod_rewrite.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_mod_rewrite). diff --git a/src/emqx_mod_subscription.erl b/src/emqx_mod_subscription.erl index c674adf97..cc4d5cda7 100644 --- a/src/emqx_mod_subscription.erl +++ b/src/emqx_mod_subscription.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_mod_subscription). diff --git a/src/emqx_mod_sup.erl b/src/emqx_mod_sup.erl index 54a77feda..cb6e86130 100644 --- a/src/emqx_mod_sup.erl +++ b/src/emqx_mod_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,11 +12,14 @@ %% 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_mod_sup). -behaviour(supervisor). +-include("types.hrl"). + -export([ start_link/0 , start_child/1 , start_child/2 @@ -25,8 +29,14 @@ -export([init/1]). %% Helper macro for declaring children of supervisor --define(CHILD(Mod, Type), {Mod, {Mod, start_link, []}, permanent, 5000, Type, [Mod]}). +-define(CHILD(Mod, Type), #{id => Mod, + start => {Mod, start_link, []}, + restart => permanent, + shutdown => 5000, + type => Type, + modules => [Mod]}). +-spec(start_link() -> startlink_ret()). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -39,13 +49,14 @@ start_child(Mod, Type) when is_atom(Mod) andalso is_atom(Type) -> -spec(stop_child(any()) -> ok | {error, term()}). stop_child(ChildId) -> case supervisor:terminate_child(?MODULE, ChildId) of - ok -> supervisor:delete_child(?MODULE, ChildId); + ok -> supervisor:delete_child(?MODULE, ChildId); Error -> Error end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Supervisor callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> {ok, {{one_for_one, 10, 100}, []}}. + diff --git a/src/emqx_modules.erl b/src/emqx_modules.erl index 79e2c02b4..c96cd6c15 100644 --- a/src/emqx_modules.erl +++ b/src/emqx_modules.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_modules). @@ -22,20 +24,25 @@ , unload/0 ]). +%% @doc Load all the extended modules. -spec(load() -> ok). load() -> ok = emqx_mod_acl_internal:load([]), - lists:foreach( - fun({Mod, Env}) -> - ok = Mod:load(Env), - ?LOG(info, "Load ~s module successfully.", [Mod]) - end, emqx_config:get_env(modules, [])). + lists:foreach(fun load/1, modules()). +load({Mod, Env}) -> + ok = Mod:load(Env), + ?LOG(info, "Load ~s module successfully.", [Mod]). + +modules() -> + emqx_config:get_env(modules, []). + +%% @doc Unload all the extended modules. -spec(unload() -> ok). unload() -> ok = emqx_mod_acl_internal:unload([]), - lists:foreach( - fun({Mod, Env}) -> - Mod:unload(Env) end, - emqx_config:get_env(modules, [])). + lists:foreach(fun unload/1, modules()). + +unload({Mod, Env}) -> + Mod:unload(Env). diff --git a/src/emqx_mountpoint.erl b/src/emqx_mountpoint.erl index 66b8da057..da8cf1f55 100644 --- a/src/emqx_mountpoint.erl +++ b/src/emqx_mountpoint.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_mountpoint). @@ -29,9 +31,9 @@ -export_type([mountpoint/0]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- mount(undefined, Any) -> Any; diff --git a/src/emqx_mqtt_caps.erl b/src/emqx_mqtt_caps.erl index ff38f687e..abd14ee3c 100644 --- a/src/emqx_mqtt_caps.erl +++ b/src/emqx_mqtt_caps.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc MQTTv5 capabilities -module(emqx_mqtt_caps). diff --git a/src/emqx_mqtt_props.erl b/src/emqx_mqtt_props.erl index 686bb5bc8..47a368714 100644 --- a/src/emqx_mqtt_props.erl +++ b/src/emqx_mqtt_props.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc MQTT5 Properties -module(emqx_mqtt_props). diff --git a/src/emqx_mqtt_types.erl b/src/emqx_mqtt_types.erl index 0274b6ac3..2fe174d11 100644 --- a/src/emqx_mqtt_types.erl +++ b/src/emqx_mqtt_types.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_mqtt_types). diff --git a/src/emqx_mqueue.erl b/src/emqx_mqueue.erl index f13ccdd34..12154c184 100644 --- a/src/emqx_mqueue.erl +++ b/src/emqx_mqueue.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,7 +12,9 @@ %% 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. +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% @doc A Simple in-memory message queue. %% %% Notice that MQTT is not a (on-disk) persistent messaging queue. @@ -42,6 +45,7 @@ %% unless `max_len' is set to `0' which implies (`infinity'). %% %% @end +%%-------------------------------------------------------------------- -module(emqx_mqueue). diff --git a/src/emqx_os_mon.erl b/src/emqx_os_mon.erl index 2b58a3749..a01d3ffcd 100644 --- a/src/emqx_os_mon.erl +++ b/src/emqx_os_mon.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_os_mon). @@ -18,19 +20,10 @@ -include("logger.hrl"). --logger_header("[OS Monitor]"). +-logger_header("[OS_MON]"). -export([start_link/1]). -%% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). - -export([ get_cpu_check_interval/0 , set_cpu_check_interval/1 , get_cpu_high_watermark/0 @@ -45,15 +38,24 @@ , set_procmem_high_watermark/1 ]). --define(OS_MON, ?MODULE). +%% gen_server callbacks +-export([ init/1 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + , terminate/2 + , code_change/3 + ]). -%%------------------------------------------------------------------------------ -%% API -%%------------------------------------------------------------------------------ +-define(OS_MON, ?MODULE). start_link(Opts) -> gen_server:start_link({local, ?OS_MON}, ?MODULE, [Opts], []). +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + get_cpu_check_interval() -> call(get_cpu_check_interval). @@ -90,9 +92,12 @@ get_procmem_high_watermark() -> set_procmem_high_watermark(Float) -> memsup:set_procmem_high_watermark(Float). -%%------------------------------------------------------------------------------ +call(Req) -> + gen_server:call(?OS_MON, Req, infinity). + +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([Opts]) -> _ = cpu_sup:util(), @@ -107,47 +112,58 @@ init([Opts]) -> handle_call(get_cpu_check_interval, _From, State) -> {reply, maps:get(cpu_check_interval, State, undefined), State}; + handle_call({set_cpu_check_interval, Seconds}, _From, State) -> {reply, ok, State#{cpu_check_interval := Seconds}}; handle_call(get_cpu_high_watermark, _From, State) -> {reply, maps:get(cpu_high_watermark, State, undefined), State}; + handle_call({set_cpu_high_watermark, Float}, _From, State) -> {reply, ok, State#{cpu_high_watermark := Float}}; handle_call(get_cpu_low_watermark, _From, State) -> {reply, maps:get(cpu_low_watermark, State, undefined), State}; + handle_call({set_cpu_low_watermark, Float}, _From, State) -> {reply, ok, State#{cpu_low_watermark := Float}}; -handle_call(_Request, _From, State) -> - {reply, ok, State}. +handle_call(Req, _From, State) -> + ?LOG(error, "[OS_MON] Unexpected call: ~p", [Req]), + {reply, ignored, State}. -handle_cast(_Request, State) -> +handle_cast(Msg, State) -> + ?LOG(error, "[OS_MON] Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info({timeout, Timer, check}, State = #{timer := Timer, - cpu_high_watermark := CPUHighWatermark, - cpu_low_watermark := CPULowWatermark, - is_cpu_alarm_set := IsCPUAlarmSet}) -> - case cpu_sup:util() of - 0 -> - {noreply, State#{timer := undefined}}; - {error, Reason} -> - ?LOG(error, "Failed to get cpu utilization: ~p", [Reason]), - {noreply, ensure_check_timer(State)}; - Busy when Busy / 100 >= CPUHighWatermark -> - alarm_handler:set_alarm({cpu_high_watermark, Busy}), - {noreply, ensure_check_timer(State#{is_cpu_alarm_set := true})}; - Busy when Busy / 100 < CPULowWatermark -> - case IsCPUAlarmSet of - true -> alarm_handler:clear_alarm(cpu_high_watermark); - false -> ok - end, - {noreply, ensure_check_timer(State#{is_cpu_alarm_set := false})}; - _Busy -> - {noreply, ensure_check_timer(State)} - end. +handle_info({timeout, Timer, check}, + State = #{timer := Timer, + cpu_high_watermark := CPUHighWatermark, + cpu_low_watermark := CPULowWatermark, + is_cpu_alarm_set := IsCPUAlarmSet}) -> + NState = case cpu_sup:util() of + 0 -> + State#{timer := undefined}; + {error, Reason} -> + ?LOG(error, "Failed to get cpu utilization: ~p", [Reason]), + ensure_check_timer(State); + Busy when (Busy / 100) >= CPUHighWatermark -> + alarm_handler:set_alarm({cpu_high_watermark, Busy}), + ensure_check_timer(State#{is_cpu_alarm_set := true}); + Busy when (Busy / 100) < CPULowWatermark -> + case IsCPUAlarmSet of + true -> alarm_handler:clear_alarm(cpu_high_watermark); + false -> ok + end, + ensure_check_timer(State#{is_cpu_alarm_set := false}); + _Busy -> + ensure_check_timer(State) + end, + {noreply, NState}; + +handle_info(Info, State) -> + ?LOG(error, "Unexpected info: ~p", [Info]), + {noreply, State}. terminate(_Reason, #{timer := Timer}) -> emqx_misc:cancel_timer(Timer). @@ -155,11 +171,9 @@ terminate(_Reason, #{timer := Timer}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ -call(Req) -> - gen_server:call(?OS_MON, Req, infinity). +%%-------------------------------------------------------------------- ensure_check_timer(State = #{cpu_check_interval := Interval}) -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}. diff --git a/src/emqx_packet.erl b/src/emqx_packet.erl index ba28bddac..a231ecd45 100644 --- a/src/emqx_packet.erl +++ b/src/emqx_packet.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_packet). @@ -40,9 +42,9 @@ protocol_name(?MQTT_PROTO_V5) -> type_name(Type) when Type > ?RESERVED andalso Type =< ?AUTH -> lists:nth(Type, ?TYPE_NAMES). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Validate MQTT Packet -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- validate(?SUBSCRIBE_PACKET(_PacketId, _Properties, [])) -> error(topic_filters_invalid); diff --git a/src/emqx_pd.erl b/src/emqx_pd.erl index 04b4d6075..5d1277833 100644 --- a/src/emqx_pd.erl +++ b/src/emqx_pd.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc The utility functions for erlang process dictionary. -module(emqx_pd). diff --git a/src/emqx_plugins.erl b/src/emqx_plugins.erl index b88fce472..e40290b5f 100644 --- a/src/emqx_plugins.erl +++ b/src/emqx_plugins.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_plugins). @@ -30,9 +32,9 @@ , load_expand_plugin/1 ]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Init plugins' config -spec(init() -> ok). @@ -47,8 +49,8 @@ init() -> init_config(CfgFile) -> {ok, [AppsEnv]} = file:consult(CfgFile), - lists:foreach(fun({AppName, Envs}) -> - [application:set_env(AppName, Par, Val) || {Par, Val} <- Envs] + lists:foreach(fun({App, Envs}) -> + [application:set_env(App, Par, Val) || {Par, Val} <- Envs] end, AppsEnv). %% @doc Load all plugins when the broker started. diff --git a/src/emqx_pmon.erl b/src/emqx_pmon.erl index 6aa880f90..411eb8ce6 100644 --- a/src/emqx_pmon.erl +++ b/src/emqx_pmon.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_pmon). @@ -33,9 +35,9 @@ -type(pmon() :: {?MODULE, map()}). -export_type([pmon/0]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(new() -> pmon()). new() -> diff --git a/src/emqx_pool.erl b/src/emqx_pool.erl index 5a6231ee0..9db0c23c4 100644 --- a/src/emqx_pool.erl +++ b/src/emqx_pool.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_pool). @@ -47,9 +49,9 @@ -type(task() :: fun() | mfa() | {fun(), Args :: list(any())}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Start pool. -spec(start_link(atom(), pos_integer()) -> startlink_ret()). @@ -87,9 +89,9 @@ cast(Msg) -> worker() -> gproc_pool:pick_worker(?POOL). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([Pool, Id]) -> true = gproc_pool:connect_worker(Pool, {Pool, Id}), @@ -123,9 +125,9 @@ terminate(_Reason, #{pool := Pool, id := Id}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- run({M, F, A}) -> erlang:apply(M, F, A); diff --git a/src/emqx_pool_sup.erl b/src/emqx_pool_sup.erl index 1e884bafe..77278807d 100644 --- a/src/emqx_pool_sup.erl +++ b/src/emqx_pool_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_pool_sup). @@ -44,7 +46,8 @@ spec(ChildId, Args) -> start_link() -> start_link(?POOL, random, {?POOL, start_link, []}). --spec(start_link(atom() | tuple(), atom(), mfa()) -> {ok, pid()} | {error, term()}). +-spec(start_link(atom() | tuple(), atom(), mfa()) + -> {ok, pid()} | {error, term()}). start_link(Pool, Type, MFA) -> start_link(Pool, Type, emqx_vm:schedulers(), MFA). @@ -54,11 +57,16 @@ start_link(Pool, Type, Size, MFA) -> supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]). init([Pool, Type, Size, {M, F, Args}]) -> - ensure_pool(Pool, Type, [{size, Size}]), + ok = ensure_pool(Pool, Type, [{size, Size}]), {ok, {{one_for_one, 10, 3600}, [ begin ensure_pool_worker(Pool, {Pool, I}, I), - {{M, I}, {M, F, [Pool, I | Args]}, transient, 5000, worker, [M]} + #{id => {M, I}, + start => {M, F, [Pool, I | Args]}, + restart => transient, + shutdown => 5000, + type => worker, + modules => [M]} end || I <- lists:seq(1, Size)]}}. ensure_pool(Pool, Type, Opts) -> diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index 6eb1bb233..f7a337541 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -51,7 +51,6 @@ proto_name, client_id, is_assigned, - conn_pid, conn_props, ack_props, username, @@ -83,15 +82,15 @@ -define(NO_PROPS, undefined). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Init -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(init(map(), list()) -> state()). -init(SocketOpts = #{ sockname := Sockname - , peername := Peername - , peercert := Peercert - , sendfun := SendFun}, Options) -> +init(SocketOpts = #{sockname := Sockname, + peername := Peername, + peercert := Peercert, + sendfun := SendFun}, Options) -> Zone = proplists:get_value(zone, Options), #pstate{zone = Zone, sendfun = SendFun, @@ -102,7 +101,6 @@ init(SocketOpts = #{ sockname := Sockname proto_name = <<"MQTT">>, client_id = <<>>, is_assigned = false, - conn_pid = self(), username = init_username(Peercert, Options), clean_start = false, topic_aliases = #{}, @@ -110,6 +108,7 @@ init(SocketOpts = #{ sockname := Sockname recv_stats = #{msg => 0, pkt => 0}, send_stats = #{msg => 0, pkt => 0}, connected = false, + %% TODO: ...? topic_alias_maximum = #{to_client => 0, from_client => 0}, conn_mod = maps:get(conn_mod, SocketOpts, undefined), credentials = #{}, @@ -128,9 +127,9 @@ set_username(Username, PState = #pstate{username = undefined}) -> set_username(_Username, PState) -> PState. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- info(PState = #pstate{zone = Zone, conn_props = ConnProps, @@ -159,19 +158,19 @@ attrs(#pstate{zone = Zone, connected_at = ConnectedAt, conn_mod = ConnMod, credentials = Credentials}) -> - #{ zone => Zone - , client_id => ClientId - , username => Username - , peername => Peername - , peercert => Peercert - , proto_ver => ProtoVer - , proto_name => ProtoName - , clean_start => CleanStart - , keepalive => Keepalive - , is_bridge => IsBridge - , connected_at => ConnectedAt - , conn_mod => ConnMod - , credentials => Credentials + #{zone => Zone, + client_id => ClientId, + username => Username, + peername => Peername, + peercert => Peercert, + proto_ver => ProtoVer, + proto_name => ProtoName, + clean_start => CleanStart, + keepalive => Keepalive, + is_bridge => IsBridge, + connected_at => ConnectedAt, + conn_mod => ConnMod, + credentials => Credentials }. attr(proto_ver, #pstate{proto_ver = ProtoVer}) -> @@ -240,8 +239,8 @@ stats(#pstate{recv_stats = #{pkt := RecvPkt, msg := RecvMsg}, {send_pkt, SendPkt}, {send_msg, SendMsg}]. -session(#pstate{session = SPid}) -> - SPid. +session(#pstate{session = Session}) -> + Session. %%------------------------------------------------------------------------------ %% Packet Received @@ -366,6 +365,7 @@ preprocess_properties(Packet, PState) -> %%------------------------------------------------------------------------------ %% Process MQTT Packet %%------------------------------------------------------------------------------ + process(?CONNECT_PACKET( #mqtt_packet_connect{proto_name = ProtoName, proto_ver = ProtoVer, @@ -405,11 +405,11 @@ process(?CONNECT_PACKET( %% Open session 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, Session, SP} -> + PState4 = PState3#pstate{session = Session, connected = true, credentials = keepsafety(Credentials0)}, - ok = emqx_cm:register_connection(client_id(PState4)), - true = emqx_cm:set_conn_attrs(client_id(PState4), attrs(PState4)), + ok = emqx_cm:register_channel(client_id(PState4)), + ok = emqx_cm:set_conn_attrs(client_id(PState4), attrs(PState4)), %% Start keepalive start_keepalive(Keepalive, PState4), %% Success @@ -466,36 +466,43 @@ process(Packet = ?PUBLISH_PACKET(?QOS_2, Topic, PacketId, _Payload), PState = #p end end; -process(?PUBACK_PACKET(PacketId, ReasonCode), PState = #pstate{session = SPid}) -> - {ok = emqx_session:puback(SPid, PacketId, ReasonCode), PState}; +process(?PUBACK_PACKET(PacketId, ReasonCode), PState = #pstate{session = Session}) -> + NSession = emqx_session:puback(PacketId, ReasonCode, Session), + {ok, PState#pstate{session = NSession}}; -process(?PUBREC_PACKET(PacketId, ReasonCode), PState = #pstate{session = SPid}) -> - case emqx_session:pubrec(SPid, PacketId, ReasonCode) of - ok -> - send(?PUBREL_PACKET(PacketId), PState); +process(?PUBREC_PACKET(PacketId, ReasonCode), PState = #pstate{session = Session}) -> + case emqx_session:pubrec(PacketId, ReasonCode, Session) of + {ok, NSession} -> + send(?PUBREL_PACKET(PacketId), PState#pstate{session = NSession}); {error, NotFound} -> send(?PUBREL_PACKET(PacketId, NotFound), PState) end; -process(?PUBREL_PACKET(PacketId, ReasonCode), PState = #pstate{session = SPid}) -> - case emqx_session:pubrel(SPid, PacketId, ReasonCode) of - ok -> - send(?PUBCOMP_PACKET(PacketId), PState); +process(?PUBREL_PACKET(PacketId, ReasonCode), PState = #pstate{session = Session}) -> + case emqx_session:pubrel(PacketId, ReasonCode, Session) of + {ok, NSession} -> + send(?PUBCOMP_PACKET(PacketId), PState#pstate{session = NSession}); {error, NotFound} -> send(?PUBCOMP_PACKET(PacketId, NotFound), PState) end; -process(?PUBCOMP_PACKET(PacketId, ReasonCode), PState = #pstate{session = SPid}) -> - {ok = emqx_session:pubcomp(SPid, PacketId, ReasonCode), PState}; +process(?PUBCOMP_PACKET(PacketId, ReasonCode), PState = #pstate{session = Session}) -> + case emqx_session:pubcomp(PacketId, ReasonCode, Session) of + {ok, NSession} -> + {ok, PState#pstate{session = NSession}}; + {error, _NotFound} -> + %% TODO: How to handle NotFound? + {ok, PState} + end; process(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters), - PState = #pstate{zone = Zone, session = SPid, credentials = Credentials}) -> + PState = #pstate{zone = Zone, proto_ver = ProtoVer, session = Session, credentials = Credentials}) -> case check_subscribe(parse_topic_filters(?SUBSCRIBE, raw_topic_filters(PState, RawTopicFilters)), PState) of {ok, TopicFilters} -> TopicFilters0 = emqx_hooks:run_fold('client.subscribe', [Credentials], TopicFilters), TopicFilters1 = emqx_mountpoint:mount(mountpoint(Credentials), TopicFilters0), - ok = emqx_session:subscribe(SPid, PacketId, Properties, TopicFilters1), - {ok, PState}; + {ok, ReasonCodes, NSession} = emqx_session:subscribe(TopicFilters1, Session), + deliver({suback, PacketId, ReasonCodes}, PState#pstate{session = NSession}); {error, TopicFilters} -> {SubTopics, ReasonCodes} = lists:foldr(fun({Topic, #{rc := ?RC_SUCCESS}}, {Topics, Codes}) -> @@ -515,26 +522,26 @@ process(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters), end; process(?UNSUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters), - PState = #pstate{session = SPid, credentials = Credentials}) -> + PState = #pstate{session = Session, credentials = Credentials}) -> TopicFilters = emqx_hooks:run_fold('client.unsubscribe', [Credentials], parse_topic_filters(?UNSUBSCRIBE, RawTopicFilters)), - ok = emqx_session:unsubscribe(SPid, PacketId, Properties, - emqx_mountpoint:mount(mountpoint(Credentials), TopicFilters)), - {ok, PState}; + TopicFilters1 = emqx_mountpoint:mount(mountpoint(Credentials), TopicFilters), + {ok, ReasonCodes, NSession} = emqx_session:unsubscribe(TopicFilters1, Session), + deliver({unsuback, PacketId, ReasonCodes}, PState#pstate{session = NSession}); process(?PACKET(?PINGREQ), PState) -> send(?PACKET(?PINGRESP), PState); process(?DISCONNECT_PACKET(?RC_SUCCESS, #{'Session-Expiry-Interval' := Interval}), - PState = #pstate{session = SPid, conn_props = #{'Session-Expiry-Interval' := OldInterval}}) -> + PState = #pstate{session = Session, conn_props = #{'Session-Expiry-Interval' := OldInterval}}) -> case Interval =/= 0 andalso OldInterval =:= 0 of true -> deliver({disconnect, ?RC_PROTOCOL_ERROR}, PState), {error, protocol_error, PState#pstate{will_msg = undefined}}; false -> - emqx_session:update_expiry_interval(SPid, Interval), + NSession = emqx_session:update_expiry_interval(Interval, Session), %% Clean willmsg - {stop, normal, PState#pstate{will_msg = undefined}} + {stop, normal, PState#pstate{will_msg = undefined, session = NSession}} end; process(?DISCONNECT_PACKET(?RC_SUCCESS), PState) -> @@ -557,19 +564,27 @@ connack({ReasonCode, PState = #pstate{proto_ver = ProtoVer, credentials = Creden _ = deliver({connack, ReasonCode1}, PState), {error, emqx_reason_codes:name(ReasonCode1, ProtoVer), PState}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Publish Message -> Broker -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- do_publish(Packet = ?PUBLISH_PACKET(QoS, PacketId), - PState = #pstate{session = SPid, credentials = Credentials}) -> + PState = #pstate{session = Session, credentials = Credentials}) -> Msg = emqx_mountpoint:mount(mountpoint(Credentials), emqx_packet:to_message(Credentials, Packet)), - puback(QoS, PacketId, emqx_session:publish(SPid, PacketId, emqx_message:set_flag(dup, false, Msg)), PState). + Msg1 = emqx_message:set_flag(dup, false, Msg), + case emqx_session:publish(PacketId, Msg1, Session) of + {ok, Result} -> + puback(QoS, PacketId, {ok, Result}, PState); + {ok, Result, NSession} -> + puback(QoS, PacketId, {ok, Result}, PState#pstate{session = NSession}); + {error, ReasonCode} -> + puback(QoS, PacketId, {error, ReasonCode}, PState) + end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Puback -> Client -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- puback(?QOS_0, _PacketId, _Result, PState) -> {ok, PState}; @@ -729,21 +744,19 @@ maybe_assign_client_id(PState) -> try_open_session(SessAttrs, PState = #pstate{zone = Zone, client_id = ClientId, - conn_pid = ConnPid, username = Username, clean_start = CleanStart}) -> - case emqx_sm:open_session( + case emqx_cm: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}; + {ok, Session} -> + {ok, Session, false}; Other -> Other end. @@ -1048,3 +1061,4 @@ do_acl_check(Action, Credentials, Topic, AllowTerm, DenyTerm) -> allow -> AllowTerm; deny -> DenyTerm end. + diff --git a/src/emqx_psk.erl b/src/emqx_psk.erl index cb8835e78..699aa2b36 100644 --- a/src/emqx_psk.erl +++ b/src/emqx_psk.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_psk). @@ -35,4 +37,4 @@ lookup(psk, ClientPSKID, _UserState) -> Except:Error:Stacktrace -> ?LOG(error, "Lookup PSK failed, ~p: ~p", [{Except,Error}, Stacktrace]), error - end. \ No newline at end of file + end. diff --git a/src/emqx_reason_codes.erl b/src/emqx_reason_codes.erl index b8a9b9e4b..a99406f53 100644 --- a/src/emqx_reason_codes.erl +++ b/src/emqx_reason_codes.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc MQTT5 reason codes -module(emqx_reason_codes). diff --git a/src/emqx_router.erl b/src/emqx_router.erl index aefe61667..f1c96b190 100644 --- a/src/emqx_router.erl +++ b/src/emqx_router.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_router). @@ -68,9 +70,9 @@ -define(ROUTE, emqx_route). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Mnesia bootstrap -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- mnesia(boot) -> ok = ekka_mnesia:create_table(?ROUTE, [ @@ -83,18 +85,18 @@ mnesia(boot) -> mnesia(copy) -> ok = ekka_mnesia:copy_table(?ROUTE). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Start a router -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(start_link(atom(), pos_integer()) -> startlink_ret()). start_link(Pool, Id) -> gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, ?MODULE, [Pool, Id], [{hibernate_after, 1000}]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Route APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(add_route(emqx_topic:topic()) -> ok | {error, term()}). add_route(Topic) when is_binary(Topic) -> @@ -183,9 +185,9 @@ call(Router, Msg) -> pick(Topic) -> gproc_pool:pick_worker(router_pool, Topic). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([Pool, Id]) -> true = gproc_pool:connect_worker(Pool, {Pool, Id}), @@ -217,9 +219,9 @@ terminate(_Reason, #{pool := Pool, id := Id}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- insert_direct_route(Route) -> mnesia:async_dirty(fun mnesia:write/3, [?ROUTE, Route, sticky_write]). diff --git a/src/emqx_router_helper.erl b/src/emqx_router_helper.erl index aac228f66..301958dc3 100644 --- a/src/emqx_router_helper.erl +++ b/src/emqx_router_helper.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_router_helper). @@ -51,9 +53,9 @@ -define(ROUTING_NODE, emqx_routing_node). -define(LOCK, {?MODULE, cleanup_routes}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Mnesia bootstrap -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- mnesia(boot) -> ok = ekka_mnesia:create_table(?ROUTING_NODE, [ @@ -66,9 +68,9 @@ mnesia(boot) -> mnesia(copy) -> ok = ekka_mnesia:copy_table(?ROUTING_NODE). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Starts the router helper -spec(start_link() -> startlink_ret()). @@ -86,9 +88,9 @@ monitor(Node) when is_atom(Node) -> false -> mnesia:dirty_write(?ROUTING_NODE, #routing_node{name = Node}) end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> ok = ekka:monitor(membership), @@ -154,9 +156,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- stats_fun() -> case ets:info(?ROUTE, size) of diff --git a/src/emqx_router_sup.erl b/src/emqx_router_sup.erl index 486d44b69..c4944e3c1 100644 --- a/src/emqx_router_sup.erl +++ b/src/emqx_router_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_router_sup). diff --git a/src/emqx_rpc.erl b/src/emqx_rpc.erl index 4d01b6229..eeb1c298e 100644 --- a/src/emqx_rpc.erl +++ b/src/emqx_rpc.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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. +%%-------------------------------------------------------------------- %% @doc wrap gen_rpc? -module(emqx_rpc). @@ -32,7 +34,7 @@ cast(Node, Mod, Fun, Args) -> filter_result(?RPC:cast(Node, Mod, Fun, Args)). filter_result(Delivery) -> - case Delivery of + case Delivery of {badrpc, Reason} -> {badrpc, Reason}; {badtcp, Reason} -> {badrpc, Reason}; _ -> Delivery diff --git a/src/emqx_sequence.erl b/src/emqx_sequence.erl index f02165ea6..29381bf6c 100644 --- a/src/emqx_sequence.erl +++ b/src/emqx_sequence.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_sequence). @@ -29,9 +31,9 @@ -export_type([seqid/0]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Create a sequence. -spec(create(name()) -> ok). diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 49454d8ac..74cbee9d0 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -14,6 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% @doc %% A stateful interaction between a Client and a Server. Some Sessions %% last only as long as the Network Connection, others can span multiple @@ -37,11 +38,10 @@ %% If the Session is currently not connected, the time at which the Session %% will end and Session State will be discarded. %% @end +%%-------------------------------------------------------------------- -module(emqx_session). --behaviour(gen_server). - -include("emqx.hrl"). -include("emqx_mqtt.hrl"). -include("logger.hrl"). @@ -49,22 +49,18 @@ -logger_header("[Session]"). --export([start_link/1]). +-export([ new/1 + , handle/2 + , close/1 + ]). -export([ info/1 , attrs/1 , stats/1 ]). --export([ resume/2 - , discard/2 - , update_expiry_interval/2 - ]). - -export([ subscribe/2 - , subscribe/4 , unsubscribe/2 - , unsubscribe/4 , publish/3 , puback/2 , puback/3 @@ -74,59 +70,43 @@ , pubcomp/3 ]). --export([close/1]). - -%% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). - --import(emqx_zone, [get_env/2, get_env/3]). - --record(state, { - %% zone - zone :: atom(), - - %% Idle timeout - idle_timeout :: pos_integer(), +-record(session, { + %% ClientId: Identifier of Session + client_id :: binary(), %% Clean Start Flag clean_start = false :: boolean(), + %% Username + username :: maybe(binary()), + %% Conn Binding: local | remote %% binding = local :: local | remote, %% Deliver fun deliver_fun :: function(), - %% ClientId: Identifier of Session - client_id :: binary(), - - %% Username - username :: maybe(binary()), - - %% Connection pid binding with session - conn_pid :: pid(), - - %% Old Connection Pid that has been kickout - old_conn_pid :: pid(), - %% Next packet id of the session next_pkt_id = 1 :: emqx_mqtt_types:packet_id(), + %% Max subscriptions + max_subscriptions :: non_neg_integer(), + %% Client’s Subscriptions. subscriptions :: map(), + %% Upgrade QoS? + upgrade_qos = false :: boolean(), + %% Client <- Broker: Inflight QoS1, QoS2 messages sent to the client but unacked. inflight :: emqx_inflight:inflight(), %% Max Inflight Size. DEPRECATED: Get from inflight %% max_inflight = 32 :: non_neg_integer(), + %% Retry interval for redelivering QoS1/2 messages + retry_interval = 20000 :: timeout(), + %% Retry Timer retry_timer :: maybe(reference()), @@ -136,334 +116,134 @@ %% Optionally, QoS 0 messages pending transmission to the Client. mqueue :: emqx_mqueue:mqueue(), + %% Max Packets Awaiting PUBREL + max_awaiting_rel = 100 :: non_neg_integer(), + + %% Awaiting PUBREL Timeout + await_rel_timeout = 20000 :: timeout(), + %% Client -> Broker: Inflight QoS2 messages received from client and waiting for pubrel. awaiting_rel :: map(), %% Awaiting PUBREL Timer await_rel_timer :: maybe(reference()), + will_msg :: emqx:message(), + + will_delay_timer :: maybe(reference()), + %% Session Expiry Interval expiry_interval = 7200 :: timeout(), %% Expired Timer expiry_timer :: maybe(reference()), - %% Stats timer - stats_timer :: maybe(reference()), - - %% GC State - gc_state, - %% Created at - created_at :: erlang:timestamp(), - - will_msg :: emqx:message(), - - will_delay_timer :: maybe(reference()) - + created_at :: erlang:timestamp() }). --type(spid() :: pid()). --type(attr() :: {atom(), term()}). +-opaque(session() :: #session{}). --export_type([attr/0]). +-export_type([session/0]). --define(DEFAULT_BATCH_N, 1000). +%% @doc Create a session. +-spec(new(Attrs :: map()) -> session()). +new(#{zone := Zone, + client_id := ClientId, + clean_start := CleanStart, + username := Username, + expiry_interval := ExpiryInterval, + max_inflight := MaxInflight, + will_msg := WillMsg}) -> + emqx_logger:set_metadata_client_id(ClientId), + #session{client_id = ClientId, + clean_start = CleanStart, + username = Username, + subscriptions = #{}, + inflight = emqx_inflight:new(MaxInflight), + mqueue = init_mqueue(Zone), + awaiting_rel = #{}, + expiry_interval = ExpiryInterval, + created_at = os:timestamp(), + will_msg = WillMsg}. -%% @doc Start a session proc. --spec(start_link(SessAttrs :: map()) -> {ok, pid()}). -start_link(SessAttrs) -> - proc_lib:start_link(?MODULE, init, [[self(), SessAttrs]]). +init_mqueue(Zone) -> + emqx_mqueue:init(#{max_len => emqx_zone:get_env(Zone, max_mqueue_len, 1000), + store_qos0 => emqx_zone:get_env(Zone, mqueue_store_qos0, true), + priorities => emqx_zone:get_env(Zone, mqueue_priorities), + default_priority => emqx_zone:get_env(Zone, mqueue_default_priority) + }). %% @doc Get session info --spec(info(spid() | #state{}) -> list({atom(), term()})). -info(SPid) when is_pid(SPid) -> - gen_server:call(SPid, info, infinity); - -info(State = #state{zone = Zone, - conn_pid = ConnPid, - next_pkt_id = PktId, - subscriptions = Subscriptions, - inflight = Inflight, - mqueue = MQueue, - awaiting_rel = AwaitingRel}) -> - attrs(State) ++ [{conn_pid, ConnPid}, - {next_pkt_id, PktId}, - {max_subscriptions, get_env(Zone, max_subscriptions, 0)}, - {subscriptions, Subscriptions}, - {upgrade_qos, get_env(Zone, upgrade_qos, false)}, - {inflight, Inflight}, - {retry_interval, get_env(Zone, retry_interval, 0)}, - {mqueue_len, emqx_mqueue:len(MQueue)}, - {awaiting_rel, AwaitingRel}, - {max_awaiting_rel, get_env(Zone, max_awaiting_rel)}, - {await_rel_timeout, get_env(Zone, await_rel_timeout)}]. +-spec(info(session()) -> list({atom(), term()})). +info(Session = #session{next_pkt_id = PktId, + max_subscriptions = MaxSubscriptions, + subscriptions = Subscriptions, + upgrade_qos = UpgradeQoS, + inflight = Inflight, + retry_interval = RetryInterval, + mqueue = MQueue, + max_awaiting_rel = MaxAwaitingRel, + awaiting_rel = AwaitingRel, + await_rel_timeout = AwaitRelTimeout}) -> + attrs(Session) ++ [{next_pkt_id, PktId}, + {max_subscriptions, MaxSubscriptions}, + {subscriptions, Subscriptions}, + {upgrade_qos, UpgradeQoS}, + {inflight, Inflight}, + {retry_interval, RetryInterval}, + {mqueue_len, emqx_mqueue:len(MQueue)}, + {awaiting_rel, AwaitingRel}, + {max_awaiting_rel, MaxAwaitingRel}, + {await_rel_timeout, AwaitRelTimeout}]. %% @doc Get session attrs --spec(attrs(spid() | #state{}) -> list({atom(), term()})). -attrs(SPid) when is_pid(SPid) -> - gen_server:call(SPid, attrs, infinity); - -attrs(#state{clean_start = CleanStart, - client_id = ClientId, - conn_pid = ConnPid, - username = Username, - expiry_interval = ExpiryInterval, - created_at = CreatedAt}) -> - [{clean_start, CleanStart}, - {binding, binding(ConnPid)}, - {client_id, ClientId}, +-spec(attrs(session()) -> list({atom(), term()})). +attrs(#session{client_id = ClientId, + clean_start = CleanStart, + username = Username, + expiry_interval = ExpiryInterval, + created_at = CreatedAt}) -> + [{client_id, ClientId}, + {clean_start, CleanStart}, {username, Username}, {expiry_interval, ExpiryInterval div 1000}, {created_at, CreatedAt}]. --spec(stats(spid() | #state{}) -> list({atom(), non_neg_integer()})). -stats(SPid) when is_pid(SPid) -> - gen_server:call(SPid, stats, infinity); - -stats(#state{zone = Zone, - subscriptions = Subscriptions, - inflight = Inflight, - mqueue = MQueue, - awaiting_rel = AwaitingRel}) -> +%% @doc Get session stats +-spec(stats(session()) -> list({atom(), non_neg_integer()})). +stats(#session{max_subscriptions = MaxSubscriptions, + subscriptions = Subscriptions, + inflight = Inflight, + mqueue = MQueue, + max_awaiting_rel = MaxAwaitingRel, + awaiting_rel = AwaitingRel}) -> lists:append(emqx_misc:proc_stats(), - [{max_subscriptions, get_env(Zone, max_subscriptions, 0)}, + [{max_subscriptions, MaxSubscriptions}, {subscriptions_count, maps:size(Subscriptions)}, {max_inflight, emqx_inflight:max_size(Inflight)}, {inflight_len, emqx_inflight:size(Inflight)}, {max_mqueue, emqx_mqueue:max_len(MQueue)}, {mqueue_len, emqx_mqueue:len(MQueue)}, {mqueue_dropped, emqx_mqueue:dropped(MQueue)}, - {max_awaiting_rel, get_env(Zone, max_awaiting_rel)}, + {max_awaiting_rel, MaxAwaitingRel}, {awaiting_rel_len, maps:size(AwaitingRel)}, {deliver_msg, emqx_pd:get_counter(deliver_stats)}, {enqueue_msg, emqx_pd:get_counter(enqueue_stats)}]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% PubSub API -%%------------------------------------------------------------------------------ - --spec(subscribe(spid(), list({emqx_topic:topic(), emqx_types:subopts()})) -> ok). -subscribe(SPid, RawTopicFilters) when is_list(RawTopicFilters) -> - TopicFilters = [emqx_topic:parse(RawTopic, maps:merge(?DEFAULT_SUBOPTS, SubOpts)) - || {RawTopic, SubOpts} <- RawTopicFilters], - subscribe(SPid, undefined, #{}, TopicFilters). - --spec(subscribe(spid(), emqx_mqtt_types:packet_id(), - emqx_mqtt_types:properties(), emqx_mqtt_types:topic_filters()) -> ok). -subscribe(SPid, PacketId, Properties, TopicFilters) -> - SubReq = {PacketId, Properties, TopicFilters}, - gen_server:cast(SPid, {subscribe, self(), SubReq}). - -%% @doc Called by connection processes when publishing messages --spec(publish(spid(), emqx_mqtt_types:packet_id(), emqx_types:message()) - -> {ok, emqx_types:deliver_results()} | {error, term()}). -publish(_SPid, _PacketId, Msg = #message{qos = ?QOS_0}) -> - %% Publish QoS0 message directly - {ok, emqx_broker:publish(Msg)}; - -publish(_SPid, _PacketId, Msg = #message{qos = ?QOS_1}) -> - %% Publish QoS1 message directly - {ok, emqx_broker:publish(Msg)}; - -publish(SPid, PacketId, Msg = #message{qos = ?QOS_2, timestamp = Ts}) -> - %% Register QoS2 message packet ID (and timestamp) to session, then publish - case gen_server:call(SPid, {register_publish_packet_id, PacketId, Ts}, infinity) of - ok -> {ok, emqx_broker:publish(Msg)}; - {error, Reason} -> {error, Reason} - end. - --spec(puback(spid(), emqx_mqtt_types:packet_id()) -> ok). -puback(SPid, PacketId) -> - gen_server:cast(SPid, {puback, PacketId, ?RC_SUCCESS}). - --spec(puback(spid(), emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code()) -> ok). -puback(SPid, PacketId, ReasonCode) -> - gen_server:cast(SPid, {puback, PacketId, ReasonCode}). - --spec(pubrec(spid(), emqx_mqtt_types:packet_id()) -> ok | {error, emqx_mqtt_types:reason_code()}). -pubrec(SPid, PacketId) -> - pubrec(SPid, PacketId, ?RC_SUCCESS). - --spec(pubrec(spid(), emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code()) - -> ok | {error, emqx_mqtt_types:reason_code()}). -pubrec(SPid, PacketId, ReasonCode) -> - gen_server:call(SPid, {pubrec, PacketId, ReasonCode}, infinity). - --spec(pubrel(spid(), emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code()) - -> ok | {error, emqx_mqtt_types:reason_code()}). -pubrel(SPid, PacketId, ReasonCode) -> - gen_server:call(SPid, {pubrel, PacketId, ReasonCode}, infinity). - --spec(pubcomp(spid(), emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code()) -> ok). -pubcomp(SPid, PacketId, ReasonCode) -> - gen_server:cast(SPid, {pubcomp, PacketId, ReasonCode}). - --spec(unsubscribe(spid(), emqx_types:topic_table()) -> ok). -unsubscribe(SPid, RawTopicFilters) when is_list(RawTopicFilters) -> - TopicFilters = lists:map(fun({RawTopic, Opts}) -> - emqx_topic:parse(RawTopic, Opts); - (RawTopic) when is_binary(RawTopic) -> - emqx_topic:parse(RawTopic) - end, RawTopicFilters), - unsubscribe(SPid, undefined, #{}, TopicFilters). - --spec(unsubscribe(spid(), emqx_mqtt_types:packet_id(), - emqx_mqtt_types:properties(), emqx_mqtt_types:topic_filters()) -> ok). -unsubscribe(SPid, PacketId, Properties, TopicFilters) -> - UnsubReq = {PacketId, Properties, TopicFilters}, - gen_server:cast(SPid, {unsubscribe, self(), UnsubReq}). - --spec(resume(spid(), map()) -> ok). -resume(SPid, SessAttrs) -> - gen_server:cast(SPid, {resume, SessAttrs}). - -%% @doc Discard the session --spec(discard(spid(), ByPid :: pid()) -> ok). -discard(SPid, ByPid) -> - gen_server:call(SPid, {discard, ByPid}, infinity). - --spec(update_expiry_interval(spid(), timeout()) -> ok). -update_expiry_interval(SPid, Interval) -> - gen_server:cast(SPid, {update_expiry_interval, Interval}). - --spec(close(spid()) -> ok). -close(SPid) -> - gen_server:call(SPid, close, infinity). - -%%------------------------------------------------------------------------------ -%% gen_server callbacks -%%------------------------------------------------------------------------------ - -init([Parent, #{zone := Zone, - client_id := ClientId, - username := Username, - conn_pid := ConnPid, - clean_start := CleanStart, - expiry_interval := ExpiryInterval, - max_inflight := MaxInflight, - will_msg := WillMsg}]) -> - process_flag(trap_exit, true), - true = link(ConnPid), - emqx_logger:set_metadata_client_id(ClientId), - GcPolicy = emqx_zone:get_env(Zone, force_gc_policy, false), - IdleTimout = get_env(Zone, idle_timeout, 30000), - State = #state{zone = Zone, - idle_timeout = IdleTimout, - clean_start = CleanStart, - deliver_fun = deliver_fun(ConnPid), - client_id = ClientId, - username = Username, - conn_pid = ConnPid, - subscriptions = #{}, - inflight = emqx_inflight:new(MaxInflight), - mqueue = init_mqueue(Zone), - awaiting_rel = #{}, - expiry_interval = ExpiryInterval, - gc_state = emqx_gc:init(GcPolicy), - created_at = os:timestamp(), - will_msg = WillMsg - }, - ok = emqx_sm:register_session(ClientId, self()), - true = emqx_sm:set_session_attrs(ClientId, attrs(State)), - true = emqx_sm:set_session_stats(ClientId, stats(State)), - ok = emqx_hooks:run('session.created', [#{client_id => ClientId}, info(State)]), - ok = emqx_misc:init_proc_mng_policy(Zone), - ok = proc_lib:init_ack(Parent, {ok, self()}), - gen_server:enter_loop(?MODULE, [{hibernate_after, IdleTimout}], State). - -init_mqueue(Zone) -> - emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000), - store_qos0 => get_env(Zone, mqueue_store_qos0, true), - priorities => get_env(Zone, mqueue_priorities), - default_priority => get_env(Zone, mqueue_default_priority) - }). - -binding(undefined) -> undefined; -binding(ConnPid) -> - case node(ConnPid) =:= node() of true -> local; false -> remote end. - -deliver_fun(ConnPid) when node(ConnPid) == node() -> - fun(Packet) -> ConnPid ! {deliver, Packet}, ok end; -deliver_fun(ConnPid) -> - Node = node(ConnPid), - fun(Packet) -> - true = emqx_rpc:cast(Node, erlang, send, [ConnPid, {deliver, Packet}]), ok - end. - -handle_call(info, _From, State) -> - reply(info(State), State); - -handle_call(attrs, _From, State) -> - reply(attrs(State), State); - -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]), - {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]), - ConnPid ! {shutdown, discard, {ClientId, ByPid}}, - {stop, {shutdown, discarded}, ok, State}; - -%% PUBLISH: This is only to register packetId to session state. -%% The actual message dispatching should be done by the caller (e.g. connection) process. -handle_call({register_publish_packet_id, PacketId, Ts}, _From, - State = #state{zone = Zone, awaiting_rel = AwaitingRel}) -> - MaxAwaitingRel = get_env(Zone, max_awaiting_rel), - reply( - case is_awaiting_full(MaxAwaitingRel, AwaitingRel) of - false -> - case maps:is_key(PacketId, AwaitingRel) of - true -> - {{error, ?RC_PACKET_IDENTIFIER_IN_USE}, State}; - false -> - State1 = State#state{awaiting_rel = maps:put(PacketId, Ts, AwaitingRel)}, - {ok, ensure_stats_timer(ensure_await_rel_timer(State1))} - end; - true -> - ?LOG(warning, "Dropped qos2 packet ~w for too many awaiting_rel", [PacketId]), - ok = emqx_metrics:inc('messages.qos2.dropped'), - {{error, ?RC_RECEIVE_MAXIMUM_EXCEEDED}, State} - end); - -%% PUBREC: -handle_call({pubrec, PacketId, _ReasonCode}, _From, State = #state{inflight = Inflight}) -> - reply( - case emqx_inflight:contain(PacketId, Inflight) of - true -> - {ok, ensure_stats_timer(acked(pubrec, PacketId, State))}; - false -> - ?LOG(warning, "The PUBREC PacketId ~w is not found.", [PacketId]), - ok = emqx_metrics:inc('packets.pubrec.missed'), - {{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}, State} - end); - -%% PUBREL: -handle_call({pubrel, PacketId, _ReasonCode}, _From, State = #state{awaiting_rel = AwaitingRel}) -> - reply( - case maps:take(PacketId, AwaitingRel) of - {_Ts, AwaitingRel1} -> - {ok, ensure_stats_timer(State#state{awaiting_rel = AwaitingRel1})}; - error -> - ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]), - ok = emqx_metrics:inc('packets.pubrel.missed'), - {{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}, State} - end); - -handle_call(close, _From, State) -> - {stop, normal, ok, State}; - -handle_call(Req, _From, State) -> - ?LOG(error, "Unexpected call: ~p", [Req]), - {reply, ignored, State}. +%%-------------------------------------------------------------------- %% SUBSCRIBE: -handle_cast({subscribe, FromPid, {PacketId, _Properties, TopicFilters}}, - State = #state{client_id = ClientId, username = Username, subscriptions = Subscriptions}) -> +-spec(subscribe(list({emqx_topic:topic(), emqx_types:subopts()}), session()) + -> {ok, list(emqx_mqtt_types:reason_code()), session()}). +subscribe(RawTopicFilters, Session = #session{client_id = ClientId, + username = Username, + subscriptions = Subscriptions}) + when is_list(RawTopicFilters) -> + TopicFilters = [emqx_topic:parse(RawTopic, maps:merge(?DEFAULT_SUBOPTS, SubOpts)) + || {RawTopic, SubOpts} <- RawTopicFilters], {ReasonCodes, Subscriptions1} = lists:foldr( fun ({Topic, SubOpts = #{qos := QoS, rc := RC}}, {RcAcc, SubMap}) when @@ -472,12 +252,173 @@ handle_cast({subscribe, FromPid, {PacketId, _Properties, TopicFilters}}, ({_Topic, #{rc := RC}}, {RcAcc, SubMap}) -> {[RC|RcAcc], SubMap} end, {[], Subscriptions}, TopicFilters), - suback(FromPid, PacketId, ReasonCodes), - noreply(ensure_stats_timer(State#state{subscriptions = Subscriptions1})); + {ok, ReasonCodes, Session#session{subscriptions = Subscriptions1}}. + +%% TODO: improve later. +do_subscribe(ClientId, Username, Topic, SubOpts, SubMap) -> + case maps:find(Topic, SubMap) of + {ok, SubOpts} -> + ok = emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => false}]), + SubMap; + {ok, _SubOpts} -> + emqx_broker:set_subopts(Topic, SubOpts), + %% Why??? + ok = 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), + ok = emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => true}]), + maps:put(Topic, SubOpts, SubMap) + end. + +%% PUBLISH: +-spec(publish(emqx_mqtt_types:packet_id(), emqx_types:message(), session()) + -> {ok, emqx_types:deliver_results()} | {error, term()}). +publish(_PacketId, Msg = #message{qos = ?QOS_0}, _Session) -> + %% Publish QoS0 message directly + {ok, emqx_broker:publish(Msg)}; + +publish(_PacketId, Msg = #message{qos = ?QOS_1}, _Session) -> + %% Publish QoS1 message directly + {ok, emqx_broker:publish(Msg)}; + +%% PUBLISH: This is only to register packetId to session state. +%% The actual message dispatching should be done by the caller. +publish(PacketId, Msg = #message{qos = ?QOS_2, timestamp = Ts}, + Session = #session{awaiting_rel = AwaitingRel, + max_awaiting_rel = MaxAwaitingRel}) -> + %% Register QoS2 message packet ID (and timestamp) to session, then publish + case is_awaiting_full(MaxAwaitingRel, AwaitingRel) of + false -> + case maps:is_key(PacketId, AwaitingRel) of + false -> + NewAwaitingRel = maps:put(PacketId, Ts, AwaitingRel), + NSession = Session#session{awaiting_rel = NewAwaitingRel}, + {ok, emqx_broker:publish(Msg), ensure_await_rel_timer(NSession)}; + true -> + {error, ?RC_PACKET_IDENTIFIER_IN_USE} + end; + true -> + ?LOG(warning, "[Session] Dropped qos2 packet ~w for too many awaiting_rel", [PacketId]), + ok = emqx_metrics:inc('messages.qos2.dropped'), + {error, ?RC_RECEIVE_MAXIMUM_EXCEEDED} + end. + +%% PUBACK: +-spec(puback(emqx_mqtt_types:packet_id(), session()) -> session()). +puback(PacketId, Session) -> + puback(PacketId, ?RC_SUCCESS, Session). + +-spec(puback(emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code(), session()) + -> session()). +puback(PacketId, ReasonCode, Session = #session{inflight = Inflight}) -> + case emqx_inflight:contain(PacketId, Inflight) of + true -> + dequeue(acked(puback, PacketId, Session)); + false -> + ?LOG(warning, "[Session] The PUBACK PacketId ~w is not found", [PacketId]), + ok = emqx_metrics:inc('packets.puback.missed'), + Session + end. + +%% PUBREC: +-spec(pubrec(emqx_mqtt_types:packet_id(), session()) + -> ok | {error, emqx_mqtt_types:reason_code()}). +pubrec(PacketId, Session) -> + pubrec(PacketId, ?RC_SUCCESS, Session). + +-spec(pubrec(emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code(), session()) + -> {ok, session()} | {error, emqx_mqtt_types:reason_code()}). +pubrec(PacketId, ReasonCode, Session = #session{inflight = Inflight}) -> + case emqx_inflight:contain(PacketId, Inflight) of + true -> + {ok, acked(pubrec, PacketId, Session)}; + false -> + ?LOG(warning, "[Session] The PUBREC PacketId ~w is not found.", [PacketId]), + ok = emqx_metrics:inc('packets.pubrec.missed'), + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end. + +%% PUBREL: +-spec(pubrel(emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code(), session()) + -> {ok, session()} | {error, emqx_mqtt_types:reason_code()}). +pubrel(PacketId, ReasonCode, Session = #session{awaiting_rel = AwaitingRel}) -> + case maps:take(PacketId, AwaitingRel) of + {_Ts, AwaitingRel1} -> + {ok, Session#session{awaiting_rel = AwaitingRel1}}; + error -> + ?LOG(warning, "[Session] The PUBREL PacketId ~w is not found", [PacketId]), + ok = emqx_metrics:inc('packets.pubrel.missed'), + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end. + +%% PUBCOMP: +-spec(pubcomp(emqx_mqtt_types:packet_id(), emqx_mqtt_types:reason_code(), session()) + -> {ok, session()} | {error, emqx_mqtt_types:reason_code()}). +pubcomp(PacketId, ReasonCode, Session = #session{inflight = Inflight}) -> + case emqx_inflight:contain(PacketId, Inflight) of + true -> + {ok, dequeue(acked(pubcomp, PacketId, Session))}; + false -> + ?LOG(warning, "[Session] The PUBCOMP PacketId ~w is not found", [PacketId]), + ok = emqx_metrics:inc('packets.pubcomp.missed'), + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end. + +%%------------------------------------------------------------------------------ +%% Awaiting ACK for QoS1/QoS2 Messages +%%------------------------------------------------------------------------------ + +await(PacketId, Msg, Session = #session{inflight = Inflight}) -> + Inflight1 = emqx_inflight:insert( + PacketId, {publish, {PacketId, Msg}, os:timestamp()}, Inflight), + ensure_retry_timer(Session#session{inflight = Inflight1}). + +acked(puback, PacketId, Session = #session{client_id = ClientId, username = Username, inflight = Inflight}) -> + case emqx_inflight:lookup(PacketId, Inflight) of + {value, {publish, {_, Msg}, _Ts}} -> + ok = emqx_hooks:run('message.acked', [#{client_id => ClientId, username => Username}, Msg]), + Session#session{inflight = emqx_inflight:delete(PacketId, Inflight)}; + none -> + ?LOG(warning, "[Session] Duplicated PUBACK PacketId ~w", [PacketId]), + Session + end; + +acked(pubrec, PacketId, Session = #session{client_id = ClientId, username = Username, inflight = Inflight}) -> + case emqx_inflight:lookup(PacketId, Inflight) of + {value, {publish, {_, Msg}, _Ts}} -> + ok = emqx_hooks:run('message.acked', [#{client_id => ClientId, username => Username}, Msg]), + Inflight1 = emqx_inflight:update(PacketId, {pubrel, PacketId, os:timestamp()}, Inflight), + Session#session{inflight = Inflight1}; + {value, {pubrel, PacketId, _Ts}} -> + ?LOG(warning, "[Session] Duplicated PUBREC PacketId ~w", [PacketId]), + Session; + none -> + ?LOG(warning, "[Session] Unexpected PUBREC PacketId ~w", [PacketId]), + Session + end; + +acked(pubcomp, PacketId, Session = #session{inflight = Inflight}) -> + case emqx_inflight:contain(PacketId, Inflight) of + true -> + Session#session{inflight = emqx_inflight:delete(PacketId, Inflight)}; + false -> + ?LOG(warning, "PUBCOMP PacketId ~w is not found", [PacketId]), + Session + end. %% UNSUBSCRIBE: -handle_cast({unsubscribe, From, {PacketId, _Properties, TopicFilters}}, - State = #state{client_id = ClientId, username = Username, subscriptions = Subscriptions}) -> +-spec(unsubscribe(emqx_mqtt_types:topic_filters(), session()) + -> {ok, list(emqx_mqtt_types:reason_code()), session()}). +unsubscribe(RawTopicFilters, Session = #session{client_id = ClientId, + username = Username, + subscriptions = Subscriptions}) + when is_list(RawTopicFilters) -> + TopicFilters = lists:map(fun({RawTopic, Opts}) -> + emqx_topic:parse(RawTopic, Opts); + (RawTopic) when is_binary(RawTopic) -> + emqx_topic:parse(RawTopic) + end, RawTopicFilters), {ReasonCodes, Subscriptions1} = lists:foldr(fun({Topic, _SubOpts}, {Acc, SubMap}) -> case maps:find(Topic, SubMap) of @@ -489,239 +430,91 @@ handle_cast({unsubscribe, From, {PacketId, _Properties, TopicFilters}}, {[?RC_NO_SUBSCRIPTION_EXISTED|Acc], SubMap} end end, {[], Subscriptions}, TopicFilters), - unsuback(From, PacketId, ReasonCodes), - noreply(ensure_stats_timer(State#state{subscriptions = Subscriptions1})); + {ok, ReasonCodes, Session#session{subscriptions = Subscriptions1}}. -%% PUBACK: -handle_cast({puback, PacketId, _ReasonCode}, State = #state{inflight = Inflight}) -> - noreply( - case emqx_inflight:contain(PacketId, Inflight) of - true -> - ensure_stats_timer(dequeue(acked(puback, PacketId, State))); - false -> - ?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]), - ok = emqx_metrics:inc('packets.puback.missed'), - State - end); +-spec(resume(map(), session()) -> session()). +resume(#{will_msg := WillMsg, + expiry_interval := ExpiryInterval, + max_inflight := MaxInflight}, + Session = #session{client_id = ClientId, + clean_start = CleanStart, + retry_timer = RetryTimer, + await_rel_timer = AwaitTimer, + expiry_timer = ExpireTimer, + will_delay_timer = WillDelayTimer}) -> -%% PUBCOMP: -handle_cast({pubcomp, PacketId, _ReasonCode}, State = #state{inflight = Inflight}) -> - noreply( - case emqx_inflight:contain(PacketId, Inflight) of - true -> - ensure_stats_timer(dequeue(acked(pubcomp, PacketId, State))); - false -> - ?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]), - ok = emqx_metrics:inc('packets.pubcomp.missed'), - State - end); - -%% RESUME: -handle_cast({resume, #{conn_pid := ConnPid, - will_msg := WillMsg, - expiry_interval := ExpiryInterval, - max_inflight := MaxInflight}}, - State = #state{client_id = ClientId, - conn_pid = OldConnPid, - clean_start = CleanStart, - retry_timer = RetryTimer, - await_rel_timer = AwaitTimer, - expiry_timer = ExpireTimer, - will_delay_timer = WillDelayTimer}) -> - - ?LOG(info, "Resumed by connection ~p ", [ConnPid]), + %% ?LOG(info, "[Session] Resumed by ~p ", [self()]), %% 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]); - ignore -> ok - end, + %% case kick(ClientId, OldConnPid, ConnPid) of + %% ok -> ?LOG(warning, "[Session] Connection ~p kickout ~p", [ConnPid, OldConnPid]); + %% ignore -> ok + %% end, - true = link(ConnPid), + Inflight = emqx_inflight:update_size(MaxInflight, Session#session.inflight), - State1 = State#state{conn_pid = ConnPid, - deliver_fun = deliver_fun(ConnPid), - old_conn_pid = OldConnPid, - clean_start = false, - retry_timer = undefined, - awaiting_rel = #{}, - await_rel_timer = undefined, - expiry_timer = undefined, - expiry_interval = ExpiryInterval, - inflight = emqx_inflight:update_size(MaxInflight, State#state.inflight), - will_delay_timer = undefined, - will_msg = WillMsg}, + Session1 = Session#session{clean_start = false, + retry_timer = undefined, + awaiting_rel = #{}, + await_rel_timer = undefined, + expiry_timer = undefined, + expiry_interval = ExpiryInterval, + inflight = Inflight, + will_delay_timer = undefined, + will_msg = WillMsg + }, %% Clean Session: true -> false??? - CleanStart andalso emqx_sm:set_session_attrs(ClientId, attrs(State1)), + CleanStart andalso emqx_cm:set_session_attrs(ClientId, attrs(Session1)), - ok = emqx_hooks:run('session.resumed', [#{client_id => ClientId}, attrs(State)]), + %%ok = emqx_hooks:run('session.resumed', [#{client_id => ClientId}, attrs(Session1)]), %% Replay delivery and Dequeue pending messages - noreply(ensure_stats_timer(dequeue(retry_delivery(true, State1)))); + dequeue(retry_delivery(true, Session1)). -handle_cast({update_expiry_interval, Interval}, State) -> - {noreply, State#state{expiry_interval = Interval}}; +-spec(update_expiry_interval(timeout(), session()) -> session()). +update_expiry_interval(Interval, Session) -> + Session#session{expiry_interval = Interval}. -handle_cast(Msg, State) -> - ?LOG(error, "Unexpected cast: ~p", [Msg]), - {noreply, State}. - -handle_info({dispatch, Topic, Msg}, State) when is_record(Msg, message) -> - handle_dispatch([{Topic, Msg}], State); - -handle_info({dispatch, Topic, Msgs}, State) when is_list(Msgs) -> - handle_dispatch([{Topic, Msg} || Msg <- Msgs], State); - -%% Do nothing if the client has been disconnected. -handle_info({timeout, Timer, retry_delivery}, State = #state{conn_pid = undefined, retry_timer = Timer}) -> - noreply(State#state{retry_timer = undefined}); - -handle_info({timeout, Timer, retry_delivery}, State = #state{retry_timer = Timer}) -> - noreply(retry_delivery(false, State#state{retry_timer = undefined})); - -handle_info({timeout, Timer, check_awaiting_rel}, State = #state{await_rel_timer = Timer}) -> - State1 = State#state{await_rel_timer = undefined}, - noreply(ensure_stats_timer(expire_awaiting_rel(State1))); - -handle_info({timeout, Timer, emit_stats}, - State = #state{client_id = ClientId, - stats_timer = Timer, - gc_state = GcState}) -> - _ = emqx_sm:set_session_stats(ClientId, stats(State)), - NewState = State#state{stats_timer = undefined}, - Limits = erlang:get(force_shutdown_policy), - case emqx_misc:conn_proc_mng_policy(Limits) of - continue -> - {noreply, NewState}; - hibernate -> - %% going to hibernate, reset gc stats - GcState1 = emqx_gc:reset(GcState), - {noreply, NewState#state{gc_state = GcState1}, hibernate}; - {shutdown, Reason} -> - ?LOG(warning, "Shutdown exceptionally due to ~p", [Reason]), - shutdown(Reason, NewState) - end; - -handle_info({timeout, Timer, expired}, State = #state{expiry_timer = Timer}) -> - ?LOG(info, "Expired, shutdown now.", []), - shutdown(expired, State); - -handle_info({timeout, Timer, will_delay}, State = #state{will_msg = WillMsg, will_delay_timer = Timer}) -> - send_willmsg(WillMsg), - {noreply, State#state{will_msg = undefined}}; - -%% ConnPid is shutting down by the supervisor. -handle_info({'EXIT', ConnPid, Reason}, #state{conn_pid = ConnPid}) - when Reason =:= killed; Reason =:= shutdown -> - exit(Reason); - -handle_info({'EXIT', ConnPid, Reason}, State = #state{will_msg = WillMsg, expiry_interval = 0, conn_pid = ConnPid}) -> - 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 = 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}) -> - %% ignore - {noreply, State#state{old_conn_pid = undefined}}; - -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]), - {noreply, State}; - -handle_info(Info, State) -> - ?LOG(error, "Unexpected info: ~p", [Info]), - {noreply, 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]], - ok = emqx_hooks:run('session.terminated', [#{client_id => ClientId, username => Username}, Reason]). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -maybe_shutdown(undefined, _Reason) -> - ok; -maybe_shutdown(Pid, normal) -> - Pid ! {shutdown, normal}; -maybe_shutdown(Pid, Reason) -> - exit(Pid, Reason). +-spec(close(session()) -> ok). +close(_Session) -> ok. %%------------------------------------------------------------------------------ %% Internal functions %%------------------------------------------------------------------------------ -is_connection_alive(#state{conn_pid = Pid}) -> - is_pid(Pid) andalso is_process_alive(Pid). -%%------------------------------------------------------------------------------ -%% Suback and unsuback - -suback(_From, undefined, _ReasonCodes) -> - ignore; -suback(From, PacketId, ReasonCodes) -> - From ! {deliver, {suback, PacketId, ReasonCodes}}. - -unsuback(_From, undefined, _ReasonCodes) -> - ignore; -unsuback(From, PacketId, ReasonCodes) -> - From ! {deliver, {unsuback, PacketId, ReasonCodes}}. - -%%------------------------------------------------------------------------------ -%% Kickout old connection - -kick(_ClientId, undefined, _ConnPid) -> - ignore; -kick(_ClientId, ConnPid, ConnPid) -> - ignore; -kick(ClientId, OldConnPid, ConnPid) -> - unlink(OldConnPid), - OldConnPid ! {shutdown, conflict, {ClientId, ConnPid}}, - %% Clean noproc - receive {'EXIT', OldConnPid, _} -> ok after 1 -> ok end. +%%deliver_fun(ConnPid) when node(ConnPid) == node() -> +%% fun(Packet) -> ConnPid ! {deliver, Packet}, ok end; +%%deliver_fun(ConnPid) -> +%% Node = node(ConnPid), +%% fun(Packet) -> +%% true = emqx_rpc:cast(Node, erlang, send, [ConnPid, {deliver, Packet}]), ok +%% end. %%------------------------------------------------------------------------------ %% Replay or Retry Delivery %% Redeliver at once if force is true -retry_delivery(Force, State = #state{inflight = Inflight}) -> +retry_delivery(Force, Session = #session{inflight = Inflight}) -> case emqx_inflight:is_empty(Inflight) of - true -> State; + true -> Session; false -> SortFun = fun({_, _, Ts1}, {_, _, Ts2}) -> Ts1 < Ts2 end, Msgs = lists:sort(SortFun, emqx_inflight:values(Inflight)), - retry_delivery(Force, Msgs, os:timestamp(), State) + retry_delivery(Force, Msgs, os:timestamp(), Session) end. -retry_delivery(_Force, [], _Now, State) -> +retry_delivery(_Force, [], _Now, Session) -> %% Retry again... - ensure_retry_timer(State); + ensure_retry_timer(Session); retry_delivery(Force, [{Type, Msg0, Ts} | Msgs], Now, - State = #state{zone = Zone, inflight = Inflight}) -> - Interval = get_env(Zone, retry_interval, 0), + Session = #session{inflight = Inflight, + retry_interval = Interval}) -> %% Microseconds -> MilliSeconds Age = timer:now_diff(Now, Ts) div 1000, if @@ -733,16 +526,16 @@ retry_delivery(Force, [{Type, Msg0, Ts} | Msgs], Now, ok = emqx_metrics:inc('messages.expired'), emqx_inflight:delete(PacketId, Inflight); false -> - redeliver({PacketId, Msg}, State), + redeliver({PacketId, Msg}, Session), emqx_inflight:update(PacketId, {publish, {PacketId, Msg}, Now}, Inflight) end; {pubrel, PacketId} -> - redeliver({pubrel, PacketId}, State), + redeliver({pubrel, PacketId}, Session), emqx_inflight:update(PacketId, {pubrel, PacketId, Now}, Inflight) end, - retry_delivery(Force, Msgs, Now, State#state{inflight = Inflight1}); + retry_delivery(Force, Msgs, Now, Session#session{inflight = Inflight1}); true -> - ensure_retry_timer(Interval - max(0, Age), State) + ensure_retry_timer(Interval - max(0, Age), Session) end. %%------------------------------------------------------------------------------ @@ -758,25 +551,26 @@ send_willmsg(WillMsg) -> %% Expire Awaiting Rel %%------------------------------------------------------------------------------ -expire_awaiting_rel(State = #state{awaiting_rel = AwaitingRel}) -> +expire_awaiting_rel(Session = #session{awaiting_rel = AwaitingRel}) -> case maps:size(AwaitingRel) of - 0 -> State; - _ -> expire_awaiting_rel(lists:keysort(2, maps:to_list(AwaitingRel)), os:timestamp(), State) + 0 -> Session; + _ -> expire_awaiting_rel(lists:keysort(2, maps:to_list(AwaitingRel)), os:timestamp(), Session) end. -expire_awaiting_rel([], _Now, State) -> - State#state{await_rel_timer = undefined}; +expire_awaiting_rel([], _Now, Session) -> + Session#session{await_rel_timer = undefined}; expire_awaiting_rel([{PacketId, Ts} | More], Now, - State = #state{zone = Zone, awaiting_rel = AwaitingRel}) -> - Timeout = get_env(Zone, await_rel_timeout), + Session = #session{awaiting_rel = AwaitingRel, + await_rel_timeout = Timeout}) -> case (timer:now_diff(Now, Ts) div 1000) of Age when Age >= Timeout -> ok = emqx_metrics:inc('messages.qos2.expired'), ?LOG(warning, "Dropped qos2 packet ~s for await_rel_timeout", [PacketId]), - expire_awaiting_rel(More, Now, State#state{awaiting_rel = maps:remove(PacketId, AwaitingRel)}); + NSession = Session#session{awaiting_rel = maps:remove(PacketId, AwaitingRel)}, + expire_awaiting_rel(More, Now, NSession); Age -> - ensure_await_rel_timer(Timeout - max(0, Age), State) + ensure_await_rel_timer(Timeout - max(0, Age), Session) end. %%------------------------------------------------------------------------------ @@ -792,69 +586,46 @@ is_awaiting_full(MaxAwaitingRel, AwaitingRel) -> %% Dispatch messages %%------------------------------------------------------------------------------ -handle_dispatch(Msgs, State = #state{inflight = Inflight, - client_id = ClientId, - username = Username, - subscriptions = SubMap}) -> +handle(Msgs, Session = #session{inflight = Inflight, + client_id = ClientId, + username = Username, + subscriptions = SubMap}) -> SessProps = #{client_id => ClientId, username => Username}, %% Drain the mailbox and batch deliver - Msgs1 = drain_m(batch_n(Inflight), Msgs), + Msgs1 = Msgs, %% drain_m(batch_n(Inflight), Msgs), %% Ack the messages for shared subscription - Msgs2 = maybe_ack_shared(Msgs1, State), + Msgs2 = maybe_ack_shared(Msgs1, Session), %% Process suboptions Msgs3 = lists:foldr( fun({Topic, Msg}, Acc) -> SubOpts = find_subopts(Topic, SubMap), - case process_subopts(SubOpts, Msg, State) of + case process_subopts(SubOpts, Msg, Session) of {ok, Msg1} -> [Msg1|Acc]; ignore -> emqx_hooks:run('message.dropped', [SessProps, Msg]), Acc end end, [], Msgs2), - NState = batch_process(Msgs3, State), - noreply(ensure_stats_timer(NState)). - -batch_n(Inflight) -> - case emqx_inflight:max_size(Inflight) of - 0 -> ?DEFAULT_BATCH_N; - Sz -> Sz - emqx_inflight:size(Inflight) - end. - -drain_m(Cnt, Msgs) when Cnt =< 0 -> - lists:reverse(Msgs); -drain_m(Cnt, Msgs) -> - receive - {dispatch, Topic, Msg} when is_record(Msg, message)-> - drain_m(Cnt-1, [{Topic, Msg} | Msgs]); - {dispatch, Topic, InMsgs} when is_list(InMsgs) -> - Msgs1 = lists:foldl( - fun(Msg, Acc) -> - [{Topic, Msg} | Acc] - end, Msgs, InMsgs), - drain_m(Cnt-length(InMsgs), Msgs1) - after 0 -> - lists:reverse(Msgs) - end. + batch_process(Msgs3, Session). %% Ack or nack the messages of shared subscription? -maybe_ack_shared(Msgs, State) when is_list(Msgs) -> +maybe_ack_shared(Msgs, Session) when is_list(Msgs) -> lists:foldr( fun({Topic, Msg}, Acc) -> - case maybe_ack_shared(Msg, State) of + case maybe_ack_shared(Msg, Session) of ok -> Acc; Msg1 -> [{Topic, Msg1}|Acc] end end, [], Msgs); -maybe_ack_shared(Msg, State) -> +maybe_ack_shared(Msg, Session) -> case emqx_shared_sub:is_ack_required(Msg) of - true -> do_ack_shared(Msg, State); + true -> do_ack_shared(Msg, Session); false -> Msg end. -do_ack_shared(Msg, State = #state{inflight = Inflight}) -> - case {is_connection_alive(State), +do_ack_shared(Msg, Session = #session{inflight = Inflight}) -> + case {true, %% is_connection_alive(Session), emqx_inflight:is_full(Inflight)} of {false, _} -> %% Require ack, but we do not have connection @@ -873,25 +644,26 @@ do_ack_shared(Msg, State = #state{inflight = Inflight}) -> emqx_shared_sub:maybe_ack(Msg) end. -process_subopts([], Msg, _State) -> +process_subopts([], Msg, _Session) -> {ok, Msg}; -process_subopts([{nl, 1}|_Opts], #message{from = ClientId}, #state{client_id = ClientId}) -> +process_subopts([{nl, 1}|_Opts], #message{from = ClientId}, #session{client_id = ClientId}) -> ignore; -process_subopts([{nl, _}|Opts], Msg, State) -> - process_subopts(Opts, Msg, State); -process_subopts([{qos, SubQoS}|Opts], Msg = #message{qos = PubQoS}, State = #state{zone = Zone}) -> - case get_env(Zone, upgrade_qos, false) of - true -> process_subopts(Opts, Msg#message{qos = max(SubQoS, PubQoS)}, State); - false -> process_subopts(Opts, Msg#message{qos = min(SubQoS, PubQoS)}, State) - end; -process_subopts([{rap, _Rap}|Opts], Msg = #message{flags = Flags, headers = #{retained := true}}, State = #state{}) -> - process_subopts(Opts, Msg#message{flags = maps:put(retain, true, Flags)}, State); -process_subopts([{rap, 0}|Opts], Msg = #message{flags = Flags}, State = #state{}) -> - process_subopts(Opts, Msg#message{flags = maps:put(retain, false, Flags)}, State); -process_subopts([{rap, _}|Opts], Msg, State) -> - process_subopts(Opts, Msg, State); -process_subopts([{subid, SubId}|Opts], Msg, State) -> - process_subopts(Opts, emqx_message:set_header('Subscription-Identifier', SubId, Msg), State). +process_subopts([{nl, _}|Opts], Msg, Session) -> + process_subopts(Opts, Msg, Session); +process_subopts([{qos, SubQoS}|Opts], Msg = #message{qos = PubQoS}, + Session = #session{upgrade_qos= true}) -> + process_subopts(Opts, Msg#message{qos = max(SubQoS, PubQoS)}, Session); +process_subopts([{qos, SubQoS}|Opts], Msg = #message{qos = PubQoS}, + Session = #session{upgrade_qos= false}) -> + process_subopts(Opts, Msg#message{qos = min(SubQoS, PubQoS)}, Session); +process_subopts([{rap, _Rap}|Opts], Msg = #message{flags = Flags, headers = #{retained := true}}, Session = #session{}) -> + process_subopts(Opts, Msg#message{flags = maps:put(retain, true, Flags)}, Session); +process_subopts([{rap, 0}|Opts], Msg = #message{flags = Flags}, Session = #session{}) -> + process_subopts(Opts, Msg#message{flags = maps:put(retain, false, Flags)}, Session); +process_subopts([{rap, _}|Opts], Msg, Session) -> + process_subopts(Opts, Msg, Session); +process_subopts([{subid, SubId}|Opts], Msg, Session) -> + process_subopts(Opts, emqx_message:set_header('Subscription-Identifier', SubId, Msg), Session). find_subopts(Topic, SubMap) -> case maps:find(Topic, SubMap) of @@ -902,43 +674,43 @@ find_subopts(Topic, SubMap) -> error -> [] end. -batch_process(Msgs, State) -> - {ok, Publishes, NState} = process_msgs(Msgs, [], State), - ok = batch_deliver(Publishes, NState), - maybe_gc(msg_cnt(Msgs), NState). +batch_process(Msgs, Session) -> + {ok, Publishes, NSession} = process_msgs(Msgs, [], Session), + ok = batch_deliver(Publishes, NSession), + NSession. -process_msgs([], Publishes, State) -> - {ok, lists:reverse(Publishes), State}; +process_msgs([], Publishes, Session) -> + {ok, lists:reverse(Publishes), Session}; -process_msgs([Msg|Msgs], Publishes, State) -> - case process_msg(Msg, State) of - {ok, Publish, NState} -> - process_msgs(Msgs, [Publish|Publishes], NState); - {ignore, NState} -> - process_msgs(Msgs, Publishes, NState) +process_msgs([Msg|Msgs], Publishes, Session) -> + case process_msg(Msg, Session) of + {ok, Publish, NSession} -> + process_msgs(Msgs, [Publish|Publishes], NSession); + {ignore, NSession} -> + process_msgs(Msgs, Publishes, NSession) end. %% Enqueue message if the client has been disconnected -process_msg(Msg, State = #state{conn_pid = undefined}) -> - {ignore, enqueue_msg(Msg, State)}; +%% process_msg(Msg, Session = #session{conn_pid = undefined}) -> +%% {ignore, enqueue_msg(Msg, Session)}; %% Prepare the qos0 message delivery -process_msg(Msg = #message{qos = ?QOS_0}, State) -> - {ok, {publish, undefined, Msg}, State}; +process_msg(Msg = #message{qos = ?QOS_0}, Session) -> + {ok, {publish, undefined, Msg}, Session}; process_msg(Msg = #message{qos = QoS}, - State = #state{next_pkt_id = PacketId, inflight = Inflight}) + Session = #session{next_pkt_id = PacketId, inflight = Inflight}) when QoS =:= ?QOS_1 orelse QoS =:= ?QOS_2 -> case emqx_inflight:is_full(Inflight) of true -> - {ignore, enqueue_msg(Msg, State)}; + {ignore, enqueue_msg(Msg, Session)}; false -> Publish = {publish, PacketId, Msg}, - NState = await(PacketId, Msg, State), - {ok, Publish, next_pkt_id(NState)} + NSession = await(PacketId, Msg, Session), + {ok, Publish, next_pkt_id(NSession)} end. -enqueue_msg(Msg, State = #state{mqueue = Q, client_id = ClientId, username = Username}) -> +enqueue_msg(Msg, Session = #session{mqueue = Q, client_id = ClientId, username = Username}) -> emqx_pd:update_counter(enqueue_stats, 1), {Dropped, NewQ} = emqx_mqueue:in(Msg, Q), if @@ -947,77 +719,41 @@ enqueue_msg(Msg, State = #state{mqueue = Q, client_id = ClientId, username = Use ok = emqx_hooks:run('message.dropped', [SessProps, Dropped]); true -> ok end, - State#state{mqueue = NewQ}. + Session#session{mqueue = NewQ}. %%------------------------------------------------------------------------------ %% Deliver %%------------------------------------------------------------------------------ -redeliver({PacketId, Msg = #message{qos = QoS}}, State) when QoS =/= ?QOS_0 -> +redeliver({PacketId, Msg = #message{qos = QoS}}, Session) when QoS =/= ?QOS_0 -> Msg1 = emqx_message:set_flag(dup, Msg), - do_deliver(PacketId, Msg1, State); + do_deliver(PacketId, Msg1, Session); -redeliver({pubrel, PacketId}, #state{deliver_fun = DeliverFun}) -> +redeliver({pubrel, PacketId}, #session{deliver_fun = DeliverFun}) -> DeliverFun({pubrel, PacketId}). -do_deliver(PacketId, Msg, #state{deliver_fun = DeliverFun}) -> +do_deliver(PacketId, Msg, #session{deliver_fun = DeliverFun}) -> emqx_pd:update_counter(deliver_stats, 1), DeliverFun({publish, PacketId, Msg}). -batch_deliver(Publishes, #state{deliver_fun = DeliverFun}) -> +batch_deliver(Publishes, #session{deliver_fun = DeliverFun}) -> emqx_pd:update_counter(deliver_stats, length(Publishes)), DeliverFun(Publishes). -%%------------------------------------------------------------------------------ -%% Awaiting ACK for QoS1/QoS2 Messages -%%------------------------------------------------------------------------------ - -await(PacketId, Msg, State = #state{inflight = Inflight}) -> - Inflight1 = emqx_inflight:insert( - PacketId, {publish, {PacketId, Msg}, os:timestamp()}, Inflight), - ensure_retry_timer(State#state{inflight = Inflight1}). - -acked(puback, PacketId, State = #state{client_id = ClientId, username = Username, inflight = Inflight}) -> - case emqx_inflight:lookup(PacketId, Inflight) of - {value, {publish, {_, Msg}, _Ts}} -> - ok = 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 - end; - -acked(pubrec, PacketId, State = #state{client_id = ClientId, username = Username, inflight = Inflight}) -> - case emqx_inflight:lookup(PacketId, Inflight) of - {value, {publish, {_, Msg}, _Ts}} -> - ok = 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; - none -> - ?LOG(warning, "Unexpected PUBREC PacketId ~w", [PacketId]), - State - end; - -acked(pubcomp, PacketId, State = #state{inflight = Inflight}) -> - State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}. - %%------------------------------------------------------------------------------ %% Dequeue %%------------------------------------------------------------------------------ -%% Do nothing if client is disconnected -dequeue(State = #state{conn_pid = undefined}) -> - State; - -dequeue(State = #state{inflight = Inflight, mqueue = Q}) -> +dequeue(Session = #session{inflight = Inflight, mqueue = Q}) -> case emqx_mqueue:is_empty(Q) orelse emqx_inflight:is_full(Inflight) of - true -> State; + true -> Session; false -> - {Msgs, Q1} = drain_q(batch_n(Inflight), [], Q), - batch_process(lists:reverse(Msgs), State#state{mqueue = Q1}) + %% TODO: + Msgs = [], + Q1 = Q, + %% {Msgs, Q1} = drain_q(batch_n(Inflight), [], Q), + batch_process(lists:reverse(Msgs), Session#session{mqueue = Q1}) end. drain_q(Cnt, Msgs, Q) when Cnt =< 0 -> @@ -1033,107 +769,45 @@ drain_q(Cnt, Msgs, Q) -> %%------------------------------------------------------------------------------ %% Ensure timers -ensure_await_rel_timer(State = #state{zone = Zone, - await_rel_timer = undefined}) -> - Timeout = get_env(Zone, await_rel_timeout), - ensure_await_rel_timer(Timeout, State); -ensure_await_rel_timer(State) -> - State. +ensure_await_rel_timer(Session = #session{await_rel_timeout = Timeout, + await_rel_timer = undefined}) -> + ensure_await_rel_timer(Timeout, Session); +ensure_await_rel_timer(Session) -> + Session. -ensure_await_rel_timer(Timeout, State = #state{await_rel_timer = undefined}) -> - State#state{await_rel_timer = emqx_misc:start_timer(Timeout, check_awaiting_rel)}; -ensure_await_rel_timer(_Timeout, State) -> - State. +ensure_await_rel_timer(Timeout, Session = #session{await_rel_timer = undefined}) -> + Session#session{await_rel_timer = emqx_misc:start_timer(Timeout, check_awaiting_rel)}; +ensure_await_rel_timer(_Timeout, Session) -> + Session. -ensure_retry_timer(State = #state{zone = Zone, retry_timer = undefined}) -> - Interval = get_env(Zone, retry_interval, 0), - ensure_retry_timer(Interval, State); -ensure_retry_timer(State) -> - State. +ensure_retry_timer(Session = #session{retry_interval = Interval, retry_timer = undefined}) -> + ensure_retry_timer(Interval, Session); +ensure_retry_timer(Session) -> + Session. -ensure_retry_timer(Interval, State = #state{retry_timer = undefined}) -> - State#state{retry_timer = emqx_misc:start_timer(Interval, retry_delivery)}; -ensure_retry_timer(_Timeout, State) -> - State. +ensure_retry_timer(Interval, Session = #session{retry_timer = undefined}) -> + Session#session{retry_timer = emqx_misc:start_timer(Interval, retry_delivery)}; +ensure_retry_timer(_Timeout, Session) -> + Session. -ensure_expire_timer(State = #state{expiry_interval = Interval}) +ensure_expire_timer(Session = #session{expiry_interval = Interval}) when Interval > 0 andalso Interval =/= 16#ffffffff -> - State#state{expiry_timer = emqx_misc:start_timer(Interval * 1000, expired)}; -ensure_expire_timer(State) -> - State. + Session#session{expiry_timer = emqx_misc:start_timer(Interval * 1000, expired)}; +ensure_expire_timer(Session) -> + Session. -ensure_will_delay_timer(State = #state{will_msg = #message{headers = #{'Will-Delay-Interval' := WillDelayInterval}}}) -> - State#state{will_delay_timer = emqx_misc:start_timer(WillDelayInterval * 1000, will_delay)}; -ensure_will_delay_timer(State = #state{will_msg = WillMsg}) -> +ensure_will_delay_timer(Session = #session{will_msg = #message{headers = #{'Will-Delay-Interval' := WillDelayInterval}}}) -> + Session#session{will_delay_timer = emqx_misc:start_timer(WillDelayInterval * 1000, will_delay)}; +ensure_will_delay_timer(Session = #session{will_msg = WillMsg}) -> send_willmsg(WillMsg), - State#state{will_msg = undefined}. + Session#session{will_msg = undefined}. -ensure_stats_timer(State = #state{zone = Zone, - stats_timer = undefined, - idle_timeout = IdleTimeout}) -> - case get_env(Zone, enable_stats, true) of - true -> State#state{stats_timer = emqx_misc:start_timer(IdleTimeout, emit_stats)}; - _Other -> State - end; -ensure_stats_timer(State) -> - State. - -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Next Packet Id -next_pkt_id(State = #state{next_pkt_id = 16#FFFF}) -> - State#state{next_pkt_id = 1}; +next_pkt_id(Session = #session{next_pkt_id = 16#FFFF}) -> + Session#session{next_pkt_id = 1}; -next_pkt_id(State = #state{next_pkt_id = Id}) -> - State#state{next_pkt_id = Id + 1}. +next_pkt_id(Session = #session{next_pkt_id = Id}) -> + Session#session{next_pkt_id = Id + 1}. -%%------------------------------------------------------------------------------ -%% Maybe GC - -msg_cnt(Msgs) -> - lists:foldl(fun(Msg, {Cnt, Oct}) -> - {Cnt+1, Oct+msg_size(Msg)} - end, {0, 0}, Msgs). - -%% Take only the payload size into account, add other fields if necessary -msg_size(#message{payload = Payload}) -> payload_size(Payload). - -%% Payload should be binary(), but not 100% sure. Need dialyzer! -payload_size(Payload) -> erlang:iolist_size(Payload). - -maybe_gc(_, State = #state{gc_state = undefined}) -> - State; -maybe_gc({Cnt, Oct}, State = #state{gc_state = GCSt}) -> - {_, GCSt1} = emqx_gc:run(Cnt, Oct, GCSt), - State#state{gc_state = GCSt1}. - -%%------------------------------------------------------------------------------ -%% Helper functions - -reply({Reply, State}) -> - reply(Reply, State). - -reply(Reply, State) -> - {reply, Reply, State}. - -noreply(State) -> - {noreply, State}. - -shutdown(Reason, State) -> - {stop, {shutdown, Reason}, State}. - -do_subscribe(ClientId, Username, Topic, SubOpts, SubMap) -> - case maps:find(Topic, SubMap) of - {ok, SubOpts} -> - ok = emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => false}]), - SubMap; - {ok, _SubOpts} -> - emqx_broker:set_subopts(Topic, SubOpts), - %% Why??? - ok = 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), - ok = emqx_hooks:run('session.subscribed', [#{client_id => ClientId, username => Username}, Topic, SubOpts#{first => true}]), - maps:put(Topic, SubOpts, SubMap) - end. diff --git a/src/emqx_session_sup.erl b/src/emqx_session_sup.erl deleted file mode 100644 index b271bd85d..000000000 --- a/src/emqx_session_sup.erl +++ /dev/null @@ -1,267 +0,0 @@ -%% Copyright (c) 2013-2019 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_session_sup). - --behaviour(gen_server). - --include("logger.hrl"). --include("types.hrl"). - --logger_header("[Session Supervisor]"). - --export([start_link/1]). - --export([ start_session/1 - , count_sessions/0 - ]). - -%% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). - --type(shutdown() :: brutal_kill | infinity | pos_integer()). - --record(state, - { sessions :: #{pid() => emqx_types:client_id()} - , mfargs :: mfa() - , shutdown :: shutdown() - , clean_down :: fun() - }). - --define(SUP, ?MODULE). --define(BATCH_EXIT, 100000). - -%% @doc Start session supervisor. --spec(start_link(map()) -> startlink_ret()). -start_link(SessSpec) when is_map(SessSpec) -> - gen_server:start_link({local, ?SUP}, ?MODULE, [SessSpec], []). - -%%------------------------------------------------------------------------------ -%% API -%%------------------------------------------------------------------------------ - -%% @doc Start a session. --spec(start_session(map()) -> startlink_ret()). -start_session(SessAttrs) -> - gen_server:call(?SUP, {start_session, SessAttrs}, infinity). - -%% @doc Count sessions. --spec(count_sessions() -> non_neg_integer()). -count_sessions() -> - gen_server:call(?SUP, count_sessions, infinity). - -%%------------------------------------------------------------------------------ -%% gen_server callbacks -%%------------------------------------------------------------------------------ - -init([Spec]) -> - process_flag(trap_exit, true), - MFA = maps:get(start, Spec), - Shutdown = maps:get(shutdown, Spec, brutal_kill), - CleanDown = maps:get(clean_down, Spec, undefined), - State = #state{sessions = #{}, - mfargs = MFA, - shutdown = Shutdown, - clean_down = CleanDown - }, - {ok, State}. - -handle_call({start_session, SessAttrs = #{client_id := ClientId}}, _From, - State = #state{sessions = SessMap, mfargs = {M, F, Args}}) -> - try erlang:apply(M, F, [SessAttrs | Args]) of - {ok, Pid} -> - reply({ok, Pid}, State#state{sessions = maps:put(Pid, ClientId, SessMap)}); - ignore -> - reply(ignore, State); - {error, Reason} -> - reply({error, Reason}, State) - catch - _:Error:Stk -> - ?LOG(error, "Failed to start session ~p: ~p, stacktrace:~n~p", - [ClientId, Error, Stk]), - reply({error, Error}, State) - end; - -handle_call(count_sessions, _From, State = #state{sessions = SessMap}) -> - {reply, maps:size(SessMap), State}; - -handle_call(Req, _From, State) -> - ?LOG(error, "Unexpected call: ~p", [Req]), - {reply, ignored, State}. - -handle_cast(Msg, State) -> - ?LOG(error, "Unexpected cast: ~p", [Msg]), - {noreply, State}. - -handle_info({'EXIT', Pid, _Reason}, State = #state{sessions = SessMap, clean_down = CleanDown}) -> - SessPids = [Pid | drain_exit(?BATCH_EXIT, [])], - {SessItems, SessMap1} = erase_all(SessPids, SessMap), - (CleanDown =:= undefined) - orelse emqx_pool:async_submit( - fun lists:foreach/2, [CleanDown, SessItems]), - {noreply, State#state{sessions = SessMap1}}; - -handle_info(Info, State) -> - ?LOG(notice, "Unexpected info: ~p", [Info]), - {noreply, State}. - -terminate(_Reason, State) -> - terminate_children(State). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%------------------------------------------------------------------------------ -%% Internal functions -%%------------------------------------------------------------------------------ - -drain_exit(0, Acc) -> - lists:reverse(Acc); -drain_exit(Cnt, Acc) -> - receive - {'EXIT', Pid, _Reason} -> - drain_exit(Cnt - 1, [Pid|Acc]) - after 0 -> - lists:reverse(Acc) - end. - -erase_all(Pids, Map) -> - lists:foldl( - fun(Pid, {Acc, M}) -> - case maps:take(Pid, M) of - {Val, M1} -> - {[{Val, Pid}|Acc], M1}; - error -> - {Acc, M} - end - end, {[], Map}, Pids). - -terminate_children(State = #state{sessions = SessMap, shutdown = Shutdown}) -> - {Pids, EStack0} = monitor_children(SessMap), - Sz = sets:size(Pids), - EStack = - case Shutdown of - brutal_kill -> - sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), - wait_children(Shutdown, Pids, Sz, undefined, EStack0); - infinity -> - sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), - wait_children(Shutdown, Pids, Sz, undefined, EStack0); - Time when is_integer(Time) -> - sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), - TRef = erlang:start_timer(Time, self(), kill), - wait_children(Shutdown, Pids, Sz, TRef, EStack0) - end, - %% Unroll stacked errors and report them - dict:fold(fun(Reason, Pid, _) -> - report_error(connection_shutdown_error, Reason, Pid, State) - end, ok, EStack). - -monitor_children(SessMap) -> - lists:foldl( - fun(Pid, {Pids, EStack}) -> - case monitor_child(Pid) of - ok -> - {sets:add_element(Pid, Pids), EStack}; - {error, normal} -> - {Pids, EStack}; - {error, Reason} -> - {Pids, dict:append(Reason, Pid, EStack)} - end - end, {sets:new(), dict:new()}, maps:keys(SessMap)). - -%% Help function to shutdown/2 switches from link to monitor approach -monitor_child(Pid) -> - %% Do the monitor operation first so that if the child dies - %% before the monitoring is done causing a 'DOWN'-message with - %% reason noproc, we will get the real reason in the 'EXIT'-message - %% unless a naughty child has already done unlink... - erlang:monitor(process, Pid), - unlink(Pid), - - receive - %% If the child dies before the unlik we must empty - %% the mail-box of the 'EXIT'-message and the 'DOWN'-message. - {'EXIT', Pid, Reason} -> - receive - {'DOWN', _, process, Pid, _} -> - {error, Reason} - end - after 0 -> - %% If a naughty child did unlink and the child dies before - %% monitor the result will be that shutdown/2 receives a - %% 'DOWN'-message with reason noproc. - %% If the child should die after the unlink there - %% will be a 'DOWN'-message with a correct reason - %% that will be handled in shutdown/2. - ok - end. - -wait_children(_Shutdown, _Pids, 0, undefined, EStack) -> - EStack; -wait_children(_Shutdown, _Pids, 0, TRef, EStack) -> - %% If the timer has expired before its cancellation, we must empty the - %% mail-box of the 'timeout'-message. - erlang:cancel_timer(TRef), - receive - {timeout, TRef, kill} -> - EStack - after 0 -> - EStack - end; - -%%TODO: Copied from supervisor.erl, rewrite it later. -wait_children(brutal_kill, Pids, Sz, TRef, EStack) -> - receive - {'DOWN', _MRef, process, Pid, killed} -> - wait_children(brutal_kill, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); - - {'DOWN', _MRef, process, Pid, Reason} -> - wait_children(brutal_kill, sets:del_element(Pid, Pids), - Sz-1, TRef, dict:append(Reason, Pid, EStack)) - end; - -wait_children(Shutdown, Pids, Sz, TRef, EStack) -> - receive - {'DOWN', _MRef, process, Pid, shutdown} -> - wait_children(Shutdown, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); - {'DOWN', _MRef, process, Pid, normal} -> - wait_children(Shutdown, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); - {'DOWN', _MRef, process, Pid, Reason} -> - wait_children(Shutdown, sets:del_element(Pid, Pids), Sz-1, - TRef, dict:append(Reason, Pid, EStack)); - {timeout, TRef, kill} -> - sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), - wait_children(Shutdown, Pids, Sz-1, undefined, EStack) - end. - -report_error(Error, Reason, Pid, #state{mfargs = MFA}) -> - SupName = list_to_atom("esockd_connection_sup - " ++ pid_to_list(self())), - ErrorMsg = [{supervisor, SupName}, - {errorContext, Error}, - {reason, Reason}, - {offender, [{pid, Pid}, - {name, connection}, - {mfargs, MFA}]}], - error_logger:error_report(supervisor_report, ErrorMsg). - -reply(Repy, State) -> - {reply, Repy, State}. - diff --git a/src/emqx_shared_sub.erl b/src/emqx_shared_sub.erl index 3b0d48189..33924888c 100644 --- a/src/emqx_shared_sub.erl +++ b/src/emqx_shared_sub.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_shared_sub). @@ -67,11 +69,12 @@ -define(no_ack, no_ack). -record(state, {pmon}). + -record(emqx_shared_subscription, {group, topic, subpid}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Mnesia bootstrap -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- mnesia(boot) -> ok = ekka_mnesia:create_table(?TAB, [ @@ -83,9 +86,9 @@ mnesia(boot) -> mnesia(copy) -> ok = ekka_mnesia:copy_table(?TAB). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(start_link() -> startlink_ret()). start_link() -> @@ -275,12 +278,12 @@ do_pick_subscriber(Group, Topic, round_robin, _ClientId, Count) -> subscribers(Group, Topic) -> ets:select(?TAB, [{{emqx_shared_subscription, Group, Topic, '$1'}, [], ['$1']}]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> - mnesia:subscribe({table, ?TAB, simple}), + {ok, _} = mnesia:subscribe({table, ?TAB, simple}), {atomic, PMon} = mnesia:transaction(fun init_monitors/0), ok = emqx_tables:new(?SHARED_SUBS, [protected, bag]), ok = emqx_tables:new(?ALIVE_SUBS, [protected, set, {read_concurrency, true}]), @@ -345,9 +348,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% keep track of alive remote pids maybe_insert_alive_tab(Pid) when ?IS_LOCAL_PID(Pid) -> ok; diff --git a/src/emqx_sm.erl b/src/emqx_sm.erl deleted file mode 100644 index 256425f5a..000000000 --- a/src/emqx_sm.erl +++ /dev/null @@ -1,297 +0,0 @@ -%% Copyright (c) 2013-2019 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_sm). - --behaviour(gen_server). - --include("emqx.hrl"). --include("logger.hrl"). --include("types.hrl"). - --logger_header("[SM]"). - -%% APIs --export([start_link/0]). - --export([ open_session/1 - , close_session/1 - , resume_session/2 - , discard_session/1 - , discard_session/2 - , register_session/1 - , register_session/2 - , unregister_session/1 - , unregister_session/2 - ]). - --export([ get_session_attrs/1 - , get_session_attrs/2 - , set_session_attrs/2 - , set_session_attrs/3 - , get_session_stats/1 - , get_session_stats/2 - , set_session_stats/2 - , set_session_stats/3 - ]). - --export([lookup_session_pids/1]). - -%% Internal functions for rpc --export([dispatch/3]). - -%% Internal function for stats --export([stats_fun/0]). - -%% Internal function for emqx_session_sup --export([clean_down/1]). - -%% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). - --define(SM, ?MODULE). - -%% ETS Tables for session management. --define(SESSION_TAB, emqx_session). --define(SESSION_P_TAB, emqx_session_p). --define(SESSION_ATTRS_TAB, emqx_session_attrs). --define(SESSION_STATS_TAB, emqx_session_stats). - --define(BATCH_SIZE, 100000). - -%%------------------------------------------------------------------------------ -%% APIs -%%------------------------------------------------------------------------------ - --spec(start_link() -> startlink_ret()). -start_link() -> - gen_server:start_link({local, ?SM}, ?MODULE, [], []). - -%% @doc Open a session. --spec(open_session(map()) -> {ok, pid()} | {ok, pid(), boolean()} | {error, term()}). -open_session(SessAttrs = #{clean_start := true, client_id := ClientId, conn_pid := ConnPid}) -> - CleanStart = fun(_) -> - ok = discard_session(ClientId, ConnPid), - emqx_session_sup:start_session(SessAttrs) - end, - emqx_sm_locker:trans(ClientId, CleanStart); - -open_session(SessAttrs = #{clean_start := false, client_id := ClientId}) -> - ResumeStart = fun(_) -> - case resume_session(ClientId, SessAttrs) of - {ok, SessPid} -> - {ok, SessPid, true}; - {error, not_found} -> - emqx_session_sup:start_session(SessAttrs) - end - end, - emqx_sm_locker:trans(ClientId, ResumeStart). - -%% @doc Discard all the sessions identified by the ClientId. --spec(discard_session(emqx_types:client_id()) -> ok). -discard_session(ClientId) when is_binary(ClientId) -> - discard_session(ClientId, self()). - --spec(discard_session(emqx_types:client_id(), pid()) -> ok). -discard_session(ClientId, ConnPid) when is_binary(ClientId) -> - lists:foreach( - fun(SessPid) -> - try emqx_session:discard(SessPid, ConnPid) - catch - _:Error:_Stk -> - ?LOG(warning, "Failed to discard ~p: ~p", [SessPid, Error]) - end - end, lookup_session_pids(ClientId)). - -%% @doc Try to resume a session. --spec(resume_session(emqx_types:client_id(), map()) -> {ok, pid()} | {error, term()}). -resume_session(ClientId, SessAttrs = #{conn_pid := ConnPid}) -> - case lookup_session_pids(ClientId) of - [] -> {error, not_found}; - [SessPid] -> - ok = emqx_session:resume(SessPid, SessAttrs), - {ok, SessPid}; - SessPids -> - [SessPid|StalePids] = lists:reverse(SessPids), - ?LOG(error, "More than one session found: ~p", [SessPids]), - lists:foreach(fun(StalePid) -> - catch emqx_session:discard(StalePid, ConnPid) - end, StalePids), - ok = emqx_session:resume(SessPid, SessAttrs), - {ok, SessPid} - end. - -%% @doc Close a session. --spec(close_session(emqx_types:client_id() | pid()) -> ok). -close_session(ClientId) when is_binary(ClientId) -> - case lookup_session_pids(ClientId) of - [] -> ok; - [SessPid] -> close_session(SessPid); - SessPids -> lists:foreach(fun close_session/1, SessPids) - end; - -close_session(SessPid) when is_pid(SessPid) -> - emqx_session:close(SessPid). - -%% @doc Register a session. --spec(register_session(emqx_types:client_id()) -> ok). -register_session(ClientId) when is_binary(ClientId) -> - register_session(ClientId, self()). - --spec(register_session(emqx_types:client_id(), pid()) -> ok). -register_session(ClientId, SessPid) when is_binary(ClientId), is_pid(SessPid) -> - Session = {ClientId, SessPid}, - true = ets:insert(?SESSION_TAB, Session), - emqx_sm_registry:register_session(Session). - -%% @doc Unregister a session --spec(unregister_session(emqx_types:client_id()) -> ok). -unregister_session(ClientId) when is_binary(ClientId) -> - unregister_session(ClientId, self()). - --spec(unregister_session(emqx_types:client_id(), pid()) -> ok). -unregister_session(ClientId, SessPid) when is_binary(ClientId), is_pid(SessPid) -> - Session = {ClientId, SessPid}, - true = ets:delete(?SESSION_STATS_TAB, Session), - true = ets:delete(?SESSION_ATTRS_TAB, Session), - true = ets:delete_object(?SESSION_P_TAB, Session), - true = ets:delete_object(?SESSION_TAB, Session), - emqx_sm_registry:unregister_session(Session). - -%% @doc Get session attrs --spec(get_session_attrs(emqx_types:client_id()) -> list(emqx_session:attr())). -get_session_attrs(ClientId) when is_binary(ClientId) -> - case lookup_session_pids(ClientId) of - [] -> []; - [SessPid|_] -> get_session_attrs(ClientId, SessPid) - end. - --spec(get_session_attrs(emqx_types:client_id(), pid()) -> list(emqx_session:attr())). -get_session_attrs(ClientId, SessPid) when is_binary(ClientId), is_pid(SessPid) -> - emqx_tables:lookup_value(?SESSION_ATTRS_TAB, {ClientId, SessPid}, []). - -%% @doc Set session attrs --spec(set_session_attrs(emqx_types:client_id(), list(emqx_session:attr())) -> true). -set_session_attrs(ClientId, SessAttrs) when is_binary(ClientId) -> - set_session_attrs(ClientId, self(), SessAttrs). - --spec(set_session_attrs(emqx_types:client_id(), pid(), list(emqx_session:attr())) -> true). -set_session_attrs(ClientId, SessPid, SessAttrs) when is_binary(ClientId), is_pid(SessPid) -> - Session = {ClientId, SessPid}, - true = ets:insert(?SESSION_ATTRS_TAB, {Session, SessAttrs}), - proplists:get_value(clean_start, SessAttrs, true) orelse ets:insert(?SESSION_P_TAB, Session). - -%% @doc Get session stats --spec(get_session_stats(emqx_types:client_id()) -> list(emqx_stats:stats())). -get_session_stats(ClientId) when is_binary(ClientId) -> - case lookup_session_pids(ClientId) of - [] -> []; - [SessPid|_] -> - get_session_stats(ClientId, SessPid) - end. - --spec(get_session_stats(emqx_types:client_id(), pid()) -> list(emqx_stats:stats())). -get_session_stats(ClientId, SessPid) when is_binary(ClientId) -> - emqx_tables:lookup_value(?SESSION_STATS_TAB, {ClientId, SessPid}, []). - -%% @doc Set session stats --spec(set_session_stats(emqx_types:client_id(), emqx_stats:stats()) -> true). -set_session_stats(ClientId, Stats) when is_binary(ClientId) -> - set_session_stats(ClientId, self(), Stats). - --spec(set_session_stats(emqx_types:client_id(), pid(), emqx_stats:stats()) -> true). -set_session_stats(ClientId, SessPid, Stats) when is_binary(ClientId), is_pid(SessPid) -> - ets:insert(?SESSION_STATS_TAB, {{ClientId, SessPid}, Stats}). - -%% @doc Lookup session pid. --spec(lookup_session_pids(emqx_types:client_id()) -> list(pid())). -lookup_session_pids(ClientId) -> - case emqx_sm_registry:is_enabled() of - true -> emqx_sm_registry:lookup_session(ClientId); - false -> - case emqx_tables:lookup_value(?SESSION_TAB, ClientId) of - undefined -> []; - SessPid when is_pid(SessPid) -> [SessPid] - end - end. - -%% @doc Dispatch a message to the session. --spec(dispatch(emqx_types:client_id(), emqx_topic:topic(), emqx_types:message()) -> any()). -dispatch(ClientId, Topic, Msg) -> - case lookup_session_pids(ClientId) of - [SessPid|_] when is_pid(SessPid) -> - SessPid ! {dispatch, Topic, Msg}; - [] -> - emqx_hooks:run('message.dropped', [#{client_id => ClientId}, Msg]) - end. - -%%------------------------------------------------------------------------------ -%% gen_server callbacks -%%------------------------------------------------------------------------------ - -init([]) -> - TabOpts = [public, set, {write_concurrency, true}], - ok = emqx_tables:new(?SESSION_TAB, [{read_concurrency, true} | TabOpts]), - ok = emqx_tables:new(?SESSION_P_TAB, TabOpts), - ok = emqx_tables:new(?SESSION_ATTRS_TAB, TabOpts), - ok = emqx_tables:new(?SESSION_STATS_TAB, TabOpts), - ok = emqx_stats:update_interval(sess_stats, fun ?MODULE:stats_fun/0), - {ok, #{}}. - -handle_call(Req, _From, State) -> - ?LOG(error, "Unexpected call: ~p", [Req]), - {reply, ignored, State}. - -handle_cast(Msg, State) -> - ?LOG(error, "Unexpected cast: ~p", [Msg]), - {noreply, State}. - -handle_info(Info, State) -> - ?LOG(error, "Unexpected info: ~p", [Info]), - {noreply, State}. - -terminate(_Reason, _State) -> - emqx_stats:cancel_update(sess_stats). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%------------------------------------------------------------------------------ -%% Internal functions -%%------------------------------------------------------------------------------ - -clean_down(Session = {ClientId, SessPid}) -> - case ets:member(?SESSION_TAB, ClientId) - orelse ets:member(?SESSION_ATTRS_TAB, Session) of - true -> - unregister_session(ClientId, SessPid); - false -> ok - end. - -stats_fun() -> - safe_update_stats(?SESSION_TAB, 'sessions.count', 'sessions.max'), - safe_update_stats(?SESSION_P_TAB, 'sessions.persistent.count', 'sessions.persistent.max'). - -safe_update_stats(Tab, Stat, MaxStat) -> - case ets:info(Tab, size) of - undefined -> ok; - Size -> emqx_stats:setstat(Stat, MaxStat, Size) - end. - diff --git a/src/emqx_sm_sup.erl b/src/emqx_sm_sup.erl deleted file mode 100644 index 65702b26f..000000000 --- a/src/emqx_sm_sup.erl +++ /dev/null @@ -1,64 +0,0 @@ -%% Copyright (c) 2013-2019 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_sm_sup). - --behaviour(supervisor). - --export([start_link/0]). - --export([init/1]). - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - %% Session locker - Locker = #{id => locker, - start => {emqx_sm_locker, start_link, []}, - restart => permanent, - shutdown => 5000, - type => worker, - modules => [emqx_sm_locker] - }, - %% Session registry - Registry = #{id => registry, - start => {emqx_sm_registry, start_link, []}, - restart => permanent, - shutdown => 5000, - type => worker, - modules => [emqx_sm_registry] - }, - %% Session Manager - Manager = #{id => manager, - start => {emqx_sm, start_link, []}, - restart => permanent, - shutdown => 5000, - type => worker, - modules => [emqx_sm] - }, - %% Session Sup - SessSpec = #{start => {emqx_session, start_link, []}, - shutdown => brutal_kill, - clean_down => fun emqx_sm:clean_down/1 - }, - SessionSup = #{id => session_sup, - start => {emqx_session_sup, start_link, [SessSpec ]}, - restart => transient, - shutdown => infinity, - type => supervisor, - modules => [emqx_session_sup] - }, - {ok, {{rest_for_one, 10, 3600}, [Locker, Registry, Manager, SessionSup]}}. - diff --git a/src/emqx_stats.erl b/src/emqx_stats.erl index a095df8d8..8db14753e 100644 --- a/src/emqx_stats.erl +++ b/src/emqx_stats.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_stats). @@ -51,7 +53,11 @@ -record(update, {name, countdown, interval, func}). --record(state, {timer, updates :: [#update{}], tick_ms :: timeout()}). +-record(state, { + timer :: reference(), + updates :: [#update{}], + tick_ms :: timeout() + }). -type(stats() :: list({atom(), non_neg_integer()})). @@ -168,9 +174,9 @@ rec(Name, Secs, UpFun) -> cast(Msg) -> gen_server:cast(?SERVER, Msg). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init(#{tick_ms := TickMs}) -> ok = emqx_tables:new(?TAB, [public, set, {write_concurrency, true}]), @@ -201,7 +207,8 @@ handle_cast({setstat, Stat, MaxStat, Val}, State) -> safe_update_element(Stat, Val), {noreply, State}; -handle_cast({update_interval, Update = #update{name = Name}}, State = #state{updates = Updates}) -> +handle_cast({update_interval, Update = #update{name = Name}}, + State = #state{updates = Updates}) -> case lists:keyfind(Name, #update.name, Updates) of #update{} -> ?LOG(warning, "Duplicated update: ~s", [Name]), @@ -242,9 +249,9 @@ terminate(_Reason, #state{timer = TRef}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- safe_update_element(Key, Val) -> try ets:update_element(?TAB, Key, {2, Val}) of @@ -256,3 +263,4 @@ safe_update_element(Key, Val) -> error:badarg -> ?LOG(warning, "Update ~p to ~p failed", [Key, Val]) end. + diff --git a/src/emqx_sup.erl b/src/emqx_sup.erl index dc30f4af1..15877502f 100644 --- a/src/emqx_sup.erl +++ b/src/emqx_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,11 +12,14 @@ %% 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_sup). -behaviour(supervisor). +-include("types.hrl"). + -export([ start_link/0 , start_child/1 , start_child/2 @@ -28,29 +32,28 @@ | {ok, supervisor:child(), term()} | {error, term()}). --define(SUPERVISOR, ?MODULE). +-define(SUP, ?MODULE). %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- +-spec(start_link() -> startlink_ret()). start_link() -> - supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). + supervisor:start_link({local, ?SUP}, ?MODULE, []). -spec(start_child(supervisor:child_spec()) -> startchild_ret()). start_child(ChildSpec) when is_tuple(ChildSpec) -> - supervisor:start_child(?SUPERVISOR, ChildSpec). + supervisor:start_child(?SUP, ChildSpec). -spec(start_child(module(), worker | supervisor) -> startchild_ret()). -start_child(Mod, worker) -> - start_child(worker_spec(Mod)); -start_child(Mod, supervisor) -> - start_child(supervisor_spec(Mod)). +start_child(Mod, Type) -> + start_child(child_spec(Mod, Type)). -spec(stop_child(supervisor:child_id()) -> ok | {error, term()}). stop_child(ChildId) -> - case supervisor:terminate_child(?SUPERVISOR, ChildId) of - ok -> supervisor:delete_child(?SUPERVISOR, ChildId); + case supervisor:terminate_child(?SUP, ChildId) of + ok -> supervisor:delete_child(?SUP, ChildId); Error -> Error end. @@ -60,32 +63,39 @@ stop_child(ChildId) -> init([]) -> %% Kernel Sup - KernelSup = supervisor_spec(emqx_kernel_sup), + KernelSup = child_spec(emqx_kernel_sup, supervisor), %% Router Sup - RouterSup = supervisor_spec(emqx_router_sup), + RouterSup = child_spec(emqx_router_sup, supervisor), %% Broker Sup - BrokerSup = supervisor_spec(emqx_broker_sup), - BridgeSup = supervisor_spec(emqx_bridge_sup), - %% Session Manager - SMSup = supervisor_spec(emqx_sm_sup), - %% Connection Manager - CMSup = supervisor_spec(emqx_cm_sup), + BrokerSup = child_spec(emqx_broker_sup, supervisor), + %% Bridge Sup + BridgeSup = child_spec(emqx_bridge_sup, supervisor), + %% CM Sup + CMSup = child_spec(emqx_cm_sup, supervisor), %% Sys Sup - SysSup = supervisor_spec(emqx_sys_sup), + SysSup = child_spec(emqx_sys_sup, supervisor), {ok, {{one_for_all, 0, 1}, - [KernelSup, - RouterSup, - BrokerSup, - BridgeSup, - SMSup, - CMSup, - SysSup]}}. + [KernelSup, RouterSup, BrokerSup, BridgeSup, CMSup, SysSup]}}. %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- -worker_spec(M) -> - {M, {M, start_link, []}, permanent, 30000, worker, [M]}. -supervisor_spec(M) -> - {M, {M, start_link, []}, permanent, infinity, supervisor, [M]}. +child_spec(Mod, supervisor) -> + #{id => Mod, + start => {Mod, start_link, []}, + restart => permanent, + shutdown => infinity, + type => supervisor, + modules => [Mod] + }; + +child_spec(Mod, worker) -> + #{id => Mod, + start => {Mod, start_link, []}, + restart => permanent, + shutdown => 15000, + type => worker, + modules => [Mod] + }. + diff --git a/src/emqx_sys.erl b/src/emqx_sys.erl index 37e22856f..2cffc7e01 100644 --- a/src/emqx_sys.erl +++ b/src/emqx_sys.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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). diff --git a/src/emqx_sys_mon.erl b/src/emqx_sys_mon.erl index f66c2a0c5..7d7bdae21 100644 --- a/src/emqx_sys_mon.erl +++ b/src/emqx_sys_mon.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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). @@ -43,18 +45,14 @@ -define(SYSMON, ?MODULE). -%%------------------------------------------------------------------------------ -%% APIs -%%------------------------------------------------------------------------------ - -%% @doc Start system monitor +%% @doc Start the system monitor. -spec(start_link(list(option())) -> startlink_ret()). start_link(Opts) -> gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([Opts]) -> erlang:system_monitor(self(), parse_opt(Opts)), diff --git a/src/emqx_sys_sup.erl b/src/emqx_sys_sup.erl index 9341b8528..cbc07650e 100644 --- a/src/emqx_sys_sup.erl +++ b/src/emqx_sys_sup.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_sup). @@ -24,23 +26,28 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - {ok, {{one_for_one, 10, 100}, [child_spec(emqx_sys, worker), - child_spec(emqx_sys_mon, worker, [emqx_config:get_env(sysmon, [])]), - child_spec(emqx_os_mon, worker, [emqx_config:get_env(os_mon, [])]), - child_spec(emqx_vm_mon, worker, [emqx_config:get_env(vm_mon, [])])]}}. + Childs = [child_spec(emqx_sys), + child_spec(emqx_sys_mon, [config(sysmon)]), + child_spec(emqx_os_mon, [config(os_mon)]), + child_spec(emqx_vm_mon, [config(vm_mon)])], + {ok, {{one_for_one, 10, 100}, Childs}}. %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- -child_spec(M, worker) -> - child_spec(M, worker, []). +child_spec(Mod) -> + child_spec(Mod, []). -child_spec(M, worker, A) -> - #{id => M, - start => {M, start_link, A}, - restart => permanent, +child_spec(Mod, Args) -> + #{id => Mod, + start => {Mod, start_link, Args}, + restart => permanent, shutdown => 5000, - type => worker, - modules => [M]}. + type => worker, + modules => [Mod] + }. + +config(Name) -> + emqx_config:get_env(Name, []). diff --git a/src/emqx_tables.erl b/src/emqx_tables.erl index 16812036a..106af13c2 100644 --- a/src/emqx_tables.erl +++ b/src/emqx_tables.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_tables). @@ -52,3 +54,4 @@ lookup_value(Tab, Key, Def) -> catch error:badarg -> Def end. + diff --git a/src/emqx_time.erl b/src/emqx_time.erl index e0ef8e5fb..e1c5e8527 100644 --- a/src/emqx_time.erl +++ b/src/emqx_time.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_time). @@ -39,3 +41,4 @@ now_ms({MegaSecs, Secs, MicroSecs}) -> ts_from_ms(Ms) -> {Ms div 1000000, Ms rem 1000000, 0}. + diff --git a/src/emqx_topic.erl b/src/emqx_topic.erl index f1d15d8e2..f3fccd70b 100644 --- a/src/emqx_topic.erl +++ b/src/emqx_topic.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_topic). @@ -43,9 +45,9 @@ -define(MAX_TOPIC_LEN, 4096). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Is wildcard topic? -spec(wildcard(topic() | words()) -> true | false). diff --git a/src/emqx_tracer.erl b/src/emqx_tracer.erl index 21562930b..2010950da 100644 --- a/src/emqx_tracer.erl +++ b/src/emqx_tracer.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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). diff --git a/src/emqx_trie.erl b/src/emqx_trie.erl index 1fb5e0ead..d437fec9d 100644 --- a/src/emqx_trie.erl +++ b/src/emqx_trie.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_trie). @@ -35,9 +37,9 @@ -define(TRIE, emqx_trie). -define(TRIE_NODE, emqx_trie_node). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Mnesia bootstrap -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Create or replicate trie tables. -spec(mnesia(boot | copy) -> ok). @@ -64,9 +66,9 @@ mnesia(copy) -> %% Copy trie_node table ok = ekka_mnesia:copy_table(?TRIE_NODE). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Trie APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Insert a topic filter into the trie. -spec(insert(emqx_topic:topic()) -> ok). @@ -111,9 +113,9 @@ delete(Topic) when is_binary(Topic) -> empty() -> ets:info(?TRIE, size) == 0. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @private %% @doc Add a path to the trie. diff --git a/src/emqx_types.erl b/src/emqx_types.erl index 50e7f78c0..57dfe7e8c 100644 --- a/src/emqx_types.erl +++ b/src/emqx_types.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_types). @@ -32,9 +34,7 @@ , protocol/0 ]). --export_type([ credentials/0 - , session/0 - ]). +-export_type([credentials/0]). -export_type([ subscription/0 , subscriber/0 @@ -65,7 +65,6 @@ share => binary(), atom() => term() }). --type(session() :: #session{}). -type(client_id() :: binary() | atom()). -type(username() :: maybe(binary())). -type(password() :: maybe(binary())). @@ -105,3 +104,4 @@ -type(alarm() :: #alarm{}). -type(plugin() :: #plugin{}). -type(command() :: #command{}). + diff --git a/src/emqx_vm.erl b/src/emqx_vm.erl index fcc9b3067..0a2489264 100644 --- a/src/emqx_vm.erl +++ b/src/emqx_vm.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_vm). diff --git a/src/emqx_vm_mon.erl b/src/emqx_vm_mon.erl index 10be9d71b..e42ceb2ee 100644 --- a/src/emqx_vm_mon.erl +++ b/src/emqx_vm_mon.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,11 +12,14 @@ %% 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_vm_mon). -behaviour(gen_server). +-include("logger.hrl"). + %% APIs -export([start_link/1]). @@ -38,13 +42,13 @@ -define(VM_MON, ?MODULE). -%%---------------------------------------------------------------------- -%% API -%%---------------------------------------------------------------------- - start_link(Opts) -> gen_server:start_link({local, ?VM_MON}, ?MODULE, [Opts], []). +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + get_check_interval() -> call(get_check_interval). @@ -63,9 +67,12 @@ get_process_low_watermark() -> set_process_low_watermark(Float) -> call({set_process_low_watermark, Float}). -%%---------------------------------------------------------------------- +call(Req) -> + gen_server:call(?VM_MON, Req, infinity). + +%%-------------------------------------------------------------------- %% gen_server callbacks -%%---------------------------------------------------------------------- +%%-------------------------------------------------------------------- init([Opts]) -> {ok, ensure_check_timer(#{check_interval => proplists:get_value(check_interval, Opts, 30), @@ -76,43 +83,53 @@ init([Opts]) -> handle_call(get_check_interval, _From, State) -> {reply, maps:get(check_interval, State, undefined), State}; + handle_call({set_check_interval, Seconds}, _From, State) -> {reply, ok, State#{check_interval := Seconds}}; handle_call(get_process_high_watermark, _From, State) -> {reply, maps:get(process_high_watermark, State, undefined), State}; + handle_call({set_process_high_watermark, Float}, _From, State) -> {reply, ok, State#{process_high_watermark := Float}}; handle_call(get_process_low_watermark, _From, State) -> {reply, maps:get(process_low_watermark, State, undefined), State}; + handle_call({set_process_low_watermark, Float}, _From, State) -> {reply, ok, State#{process_low_watermark := Float}}; -handle_call(_Request, _From, State) -> - {reply, ok, State}. +handle_call(Req, _From, State) -> + ?LOG(error, "[VM_MON] Unexpected call: ~p", [Req]), + {reply, ignored, State}. -handle_cast(_Request, State) -> +handle_cast(Msg, State) -> + ?LOG(error, "[VM_MON] Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info({timeout, Timer, check}, State = #{timer := Timer, - process_high_watermark := ProcHighWatermark, - process_low_watermark := ProcLowWatermark, - is_process_alarm_set := IsProcessAlarmSet}) -> +handle_info({timeout, Timer, check}, + State = #{timer := Timer, + process_high_watermark := ProcHighWatermark, + process_low_watermark := ProcLowWatermark, + is_process_alarm_set := IsProcessAlarmSet}) -> ProcessCount = erlang:system_info(process_count), - case ProcessCount / erlang:system_info(process_limit) of - Percent when Percent >= ProcHighWatermark -> - alarm_handler:set_alarm({too_many_processes, ProcessCount}), - {noreply, ensure_check_timer(State#{is_process_alarm_set := true})}; - Percent when Percent < ProcLowWatermark -> - case IsProcessAlarmSet of - true -> alarm_handler:clear_alarm(too_many_processes); - false -> ok - end, - {noreply, ensure_check_timer(State#{is_process_alarm_set := false})}; - _Precent -> - {noreply, ensure_check_timer(State)} - end. + NState = case ProcessCount / erlang:system_info(process_limit) of + Percent when Percent >= ProcHighWatermark -> + alarm_handler:set_alarm({too_many_processes, ProcessCount}), + State#{is_process_alarm_set := true}; + Percent when Percent < ProcLowWatermark -> + case IsProcessAlarmSet of + true -> alarm_handler:clear_alarm(too_many_processes); + false -> ok + end, + State#{is_process_alarm_set := false}; + _Precent -> State + end, + {noreply, ensure_check_timer(NState)}; + +handle_info(Info, State) -> + ?LOG(error, "[VM_MON] Unexpected info: ~p", [Info]), + {noreply, State}. terminate(_Reason, #{timer := Timer}) -> emqx_misc:cancel_timer(Timer). @@ -120,11 +137,10 @@ terminate(_Reason, #{timer := Timer}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%---------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% Internal functions -%%---------------------------------------------------------------------- -call(Req) -> - gen_server:call(?VM_MON, Req, infinity). +%%-------------------------------------------------------------------- ensure_check_timer(State = #{check_interval := Interval}) -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}. + diff --git a/src/emqx_ws_channel.erl b/src/emqx_ws_channel.erl index a97cff326..fcf145341 100644 --- a/src/emqx_ws_channel.erl +++ b/src/emqx_ws_channel.erl @@ -148,8 +148,7 @@ websocket_init(#state{request = Req, options = Options}) -> ?LOG(error, "Illegal cookie"), undefined; Error:Reason -> - ?LOG(error, - "Cookie is parsed failed, Error: ~p, Reason ~p", + ?LOG(error, "Cookie is parsed failed, Error: ~p, Reason ~p", [Error, Reason]), undefined end, @@ -346,3 +345,4 @@ shutdown(Reason, State) -> wsock_stats() -> [{Key, emqx_pd:get_counter(Key)} || Key <- ?SOCK_STATS]. + diff --git a/src/emqx_zone.erl b/src/emqx_zone.erl index d654487c5..c0ad66312 100644 --- a/src/emqx_zone.erl +++ b/src/emqx_zone.erl @@ -1,4 +1,5 @@ -%% Copyright (c) 2013-2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +%% Copyright (c) 2019 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. @@ -11,6 +12,7 @@ %% 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_zone). @@ -50,9 +52,9 @@ -define(SERVER, ?MODULE). -define(KEY(Zone, Key), {?MODULE, Zone, Key}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% APIs -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -spec(start_link() -> startlink_ret()). start_link() -> @@ -85,9 +87,9 @@ force_reload() -> stop() -> gen_server:stop(?SERVER, normal, infinity). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% gen_server callbacks -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- init([]) -> _ = do_reload(), @@ -119,9 +121,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Internal functions -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- do_reload() -> [ persistent_term:put(?KEY(Zone, Key), Val) diff --git a/test/emqx_ws_channel_SUITE.erl b/test/emqx_ws_channel_SUITE.erl index 29f02e653..1b2f4dc62 100644 --- a/test/emqx_ws_channel_SUITE.erl +++ b/test/emqx_ws_channel_SUITE.erl @@ -23,11 +23,6 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --define(CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{ - client_id = <<"mqtt_client">>, - username = <<"admin">>, - password = <<"public">>})). - all() -> [ t_ws_connect_api , t_ws_auth_failure @@ -44,17 +39,13 @@ t_ws_auth_failure(_Config) -> application:set_env(emqx, allow_anonymous, false), 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, CONNACK} = rfc6455_client:recv(WS), - {ok, ?CONNACK_PACKET(?CONNACK_AUTH), <<>>, _} = raw_recv_pase(CONNACK), - application:set_env(emqx, allow_anonymous, true), - ok. - -t_ws_connect_api(_Config) -> - WS = rfc6455_client:new("ws://127.0.0.1:8083" ++ "/mqtt", self()), - {ok, _} = rfc6455_client:open(WS), - ok = rfc6455_client:send_binary(WS, raw_send_serialize(?CLIENT)), + Connect = ?CONNECT_PACKET( + #mqtt_packet_connect{ + client_id = <<"mqtt_client">>, + username = <<"admin">>, + password = <<"public">> + }), + ok = rfc6455_client:send_binary(WS, raw_send_serialize(Connect)), {binary, Bin} = rfc6455_client:recv(WS), Connack = ?CONNACK_PACKET(?CONNACK_ACCEPT), {ok, Connack, <<>>, _} = raw_recv_pase(Bin), @@ -97,3 +88,4 @@ t_stats(StatsData) -> ?assertEqual(true, proplists:get_value(recv_pkt, StatsData) =:=1), ?assertEqual(true, proplists:get_value(recv_msg, StatsData) >=0), ?assertEqual(true, proplists:get_value(send_pkt, StatsData) =:=1). +