Implement the channel architecture
This commit is contained in:
parent
21162f88b8
commit
7774b85f81
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-ifndef(EMQ_X_HRL).
|
-ifndef(EMQ_X_HRL).
|
||||||
-define(EMQ_X_HRL, true).
|
-define(EMQ_X_HRL, true).
|
||||||
|
@ -19,10 +21,6 @@
|
||||||
%% Banner
|
%% 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(PROTOCOL_VERSION, "MQTT/5.0").
|
||||||
|
|
||||||
-define(ERTS_MINIMUM_REQUIRED, "10.0").
|
-define(ERTS_MINIMUM_REQUIRED, "10.0").
|
||||||
|
@ -47,8 +45,6 @@
|
||||||
%% Message and Delivery
|
%% Message and Delivery
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-record(session, {sid, pid}).
|
|
||||||
|
|
||||||
-record(subscription, {topic, subid, subopts}).
|
-record(subscription, {topic, subid, subopts}).
|
||||||
|
|
||||||
%% See 'Application Message' in MQTT Version 5.0
|
%% See 'Application Message' in MQTT Version 5.0
|
||||||
|
@ -72,9 +68,12 @@
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-record(delivery, {
|
-record(delivery, {
|
||||||
sender :: pid(), %% Sender of the delivery
|
%% Sender of the delivery
|
||||||
message :: #message{}, %% The message delivered
|
sender :: pid(),
|
||||||
results :: list() %% Dispatches of the message
|
%% The message delivered
|
||||||
|
message :: #message{},
|
||||||
|
%% Dispatches of the message
|
||||||
|
results :: list()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -151,6 +150,7 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Banned
|
%% Banned
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-type(banned_who() :: {client_id, binary()}
|
-type(banned_who() :: {client_id, binary()}
|
||||||
| {username, binary()}
|
| {username, binary()}
|
||||||
| {ip_address, inet:ip_address()}).
|
| {ip_address, inet:ip_address()}).
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_banned).
|
-module(emqx_banned).
|
||||||
|
|
||||||
|
@ -42,14 +44,14 @@
|
||||||
, code_change/3
|
, code_change/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(TAB, ?MODULE).
|
-define(BANNED_TAB, ?MODULE).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Mnesia bootstrap
|
%% Mnesia bootstrap
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
mnesia(boot) ->
|
mnesia(boot) ->
|
||||||
ok = ekka_mnesia:create_table(?TAB, [
|
ok = ekka_mnesia:create_table(?BANNED_TAB, [
|
||||||
{type, set},
|
{type, set},
|
||||||
{disc_copies, [node()]},
|
{disc_copies, [node()]},
|
||||||
{record_name, banned},
|
{record_name, banned},
|
||||||
|
@ -57,7 +59,7 @@ mnesia(boot) ->
|
||||||
{storage_properties, [{ets, [{read_concurrency, true}]}]}]);
|
{storage_properties, [{ets, [{read_concurrency, true}]}]}]);
|
||||||
|
|
||||||
mnesia(copy) ->
|
mnesia(copy) ->
|
||||||
ok = ekka_mnesia:copy_table(?TAB).
|
ok = ekka_mnesia:copy_table(?BANNED_TAB).
|
||||||
|
|
||||||
%% @doc Start the banned server.
|
%% @doc Start the banned server.
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
|
@ -66,41 +68,42 @@ start_link() ->
|
||||||
|
|
||||||
-spec(check(emqx_types:credentials()) -> boolean()).
|
-spec(check(emqx_types:credentials()) -> boolean()).
|
||||||
check(#{client_id := ClientId, username := Username, peername := {IPAddr, _}}) ->
|
check(#{client_id := ClientId, username := Username, peername := {IPAddr, _}}) ->
|
||||||
ets:member(?TAB, {client_id, ClientId})
|
ets:member(?BANNED_TAB, {client_id, ClientId})
|
||||||
orelse ets:member(?TAB, {username, Username})
|
orelse ets:member(?BANNED_TAB, {username, Username})
|
||||||
orelse ets:member(?TAB, {ipaddr, IPAddr}).
|
orelse ets:member(?BANNED_TAB, {ipaddr, IPAddr}).
|
||||||
|
|
||||||
-spec(add(emqx_types:banned()) -> ok).
|
-spec(add(emqx_types:banned()) -> ok).
|
||||||
add(Banned) when is_record(Banned, banned) ->
|
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()}
|
-spec(delete({client_id, emqx_types:client_id()}
|
||||||
| {username, emqx_types:username()}
|
| {username, emqx_types:username()}
|
||||||
| {peername, emqx_types:peername()}) -> ok).
|
| {peername, emqx_types:peername()}) -> ok).
|
||||||
delete(Key) ->
|
delete(Key) ->
|
||||||
mnesia:dirty_delete(?TAB, Key).
|
mnesia:dirty_delete(?BANNED_TAB, Key).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, ensure_expiry_timer(#{expiry_timer => undefined})}.
|
{ok, ensure_expiry_timer(#{expiry_timer => undefined})}.
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
?LOG(error, "[Banned] unexpected call: ~p", [Req]),
|
?LOG(error, "[Banned] Unexpected call: ~p", [Req]),
|
||||||
{reply, ignored, State}.
|
{reply, ignored, State}.
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
?LOG(error, "[Banned] unexpected msg: ~p", [Msg]),
|
?LOG(error, "[Banned] Unexpected msg: ~p", [Msg]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) ->
|
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};
|
{noreply, ensure_expiry_timer(State), hibernate};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
?LOG(error, "[Banned] unexpected info: ~p", [Info]),
|
?LOG(error, "[Banned] Unexpected info: ~p", [Info]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, #{expiry_timer := TRef}) ->
|
terminate(_Reason, #{expiry_timer := TRef}) ->
|
||||||
|
@ -109,9 +112,9 @@ terminate(_Reason, #{expiry_timer := TRef}) ->
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
ensure_expiry_timer(State) ->
|
ensure_expiry_timer(State) ->
|
||||||
|
@ -124,6 +127,7 @@ ensure_expiry_timer(State) ->
|
||||||
expire_banned_items(Now) ->
|
expire_banned_items(Now) ->
|
||||||
mnesia:foldl(
|
mnesia:foldl(
|
||||||
fun(B = #banned{until = Until}, _Acc) when Until < Now ->
|
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
|
(_, _Acc) -> ok
|
||||||
end, ok, ?TAB).
|
end, ok, ?BANNED_TAB).
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,10 @@
|
||||||
, stats/1
|
, stats/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([kick/1]).
|
-export([ kick/1
|
||||||
|
, discard/1
|
||||||
|
, takeover/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export([session/1]).
|
-export([session/1]).
|
||||||
|
|
||||||
|
@ -135,6 +138,12 @@ stats(#state{transport = Transport,
|
||||||
kick(CPid) ->
|
kick(CPid) ->
|
||||||
call(CPid, kick).
|
call(CPid, kick).
|
||||||
|
|
||||||
|
discard(CPid) ->
|
||||||
|
call(CPid, discard).
|
||||||
|
|
||||||
|
takeover(CPid) ->
|
||||||
|
call(CPid, takeover).
|
||||||
|
|
||||||
session(CPid) ->
|
session(CPid) ->
|
||||||
call(CPid, session).
|
call(CPid, session).
|
||||||
|
|
||||||
|
@ -284,6 +293,10 @@ handle({call, From}, kick, State) ->
|
||||||
ok = gen_statem:reply(From, ok),
|
ok = gen_statem:reply(From, ok),
|
||||||
shutdown(kicked, State);
|
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}) ->
|
handle({call, From}, session, State = #state{proto_state = ProtoState}) ->
|
||||||
reply(From, emqx_protocol:session(ProtoState), State);
|
reply(From, emqx_protocol:session(ProtoState), State);
|
||||||
|
|
||||||
|
|
418
src/emqx_cm.erl
418
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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% Channel Manager
|
||||||
-module(emqx_cm).
|
-module(emqx_cm).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
@ -22,25 +25,39 @@
|
||||||
|
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
|
||||||
-export([ register_connection/1
|
-export([ register_channel/1
|
||||||
, register_connection/2
|
, unregister_channel/1
|
||||||
, unregister_connection/1
|
, unregister_channel/2
|
||||||
, unregister_connection/2
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ get_conn_attrs/1
|
-export([ get_conn_attrs/1
|
||||||
, get_conn_attrs/2
|
, get_conn_attrs/2
|
||||||
, set_conn_attrs/2
|
, set_conn_attrs/2
|
||||||
, set_conn_attrs/3
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ get_conn_stats/1
|
-export([ get_conn_stats/1
|
||||||
, get_conn_stats/2
|
, get_conn_stats/2
|
||||||
, set_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
|
%% gen_server callbacks
|
||||||
-export([ init/1
|
-export([ init/1
|
||||||
|
@ -51,159 +68,350 @@
|
||||||
, code_change/3
|
, code_change/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% internal export
|
%% Internal export
|
||||||
-export([stats_fun/0]).
|
-export([stats_fun/0]).
|
||||||
|
|
||||||
-define(CM, ?MODULE).
|
-type(chan_pid() :: pid()).
|
||||||
|
|
||||||
%% ETS tables for connection management.
|
-opaque(attrs() :: #{atom() => term()}).
|
||||||
-define(CONN_TAB, emqx_conn).
|
|
||||||
-define(CONN_ATTRS_TAB, emqx_conn_attrs).
|
|
||||||
-define(CONN_STATS_TAB, emqx_conn_stats).
|
|
||||||
|
|
||||||
|
-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).
|
-define(BATCH_SIZE, 100000).
|
||||||
|
|
||||||
%% @doc Start the connection manager.
|
%% Server name
|
||||||
|
-define(CM, ?MODULE).
|
||||||
|
|
||||||
|
%% @doc Start the channel manager.
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?CM}, ?MODULE, [], []).
|
gen_server:start_link({local, ?CM}, ?MODULE, [], []).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Register a connection.
|
%% @doc Register a channel.
|
||||||
-spec(register_connection(emqx_types:client_id()) -> ok).
|
-spec(register_channel(emqx_types:client_id()) -> ok).
|
||||||
register_connection(ClientId) when is_binary(ClientId) ->
|
register_channel(ClientId) when is_binary(ClientId) ->
|
||||||
register_connection(ClientId, self()).
|
register_channel(ClientId, self()).
|
||||||
|
|
||||||
-spec(register_connection(emqx_types:client_id(), pid()) -> ok).
|
-spec(register_channel(emqx_types:client_id(), chan_pid()) -> ok).
|
||||||
register_connection(ClientId, ConnPid) when is_binary(ClientId), is_pid(ConnPid) ->
|
register_channel(ClientId, ChanPid) ->
|
||||||
true = ets:insert(?CONN_TAB, {ClientId, ConnPid}),
|
Chan = {ClientId, ChanPid},
|
||||||
notify({registered, ClientId, ConnPid}).
|
true = ets:insert(?CHAN_TAB, Chan),
|
||||||
|
ok = emqx_cm_registry:register_channel(Chan),
|
||||||
|
cast({registered, Chan}).
|
||||||
|
|
||||||
%% @doc Unregister a connection.
|
%% @doc Unregister a channel.
|
||||||
-spec(unregister_connection(emqx_types:client_id()) -> ok).
|
-spec(unregister_channel(emqx_types:client_id()) -> ok).
|
||||||
unregister_connection(ClientId) when is_binary(ClientId) ->
|
unregister_channel(ClientId) when is_binary(ClientId) ->
|
||||||
unregister_connection(ClientId, self()).
|
unregister_channel(ClientId, self()).
|
||||||
|
|
||||||
-spec(unregister_connection(emqx_types:client_id(), pid()) -> ok).
|
-spec(unregister_channel(emqx_types:client_id(), chan_pid()) -> ok).
|
||||||
unregister_connection(ClientId, ConnPid) when is_binary(ClientId), is_pid(ConnPid) ->
|
unregister_channel(ClientId, ChanPid) ->
|
||||||
true = do_unregister_connection({ClientId, ConnPid}),
|
Chan = {ClientId, ChanPid},
|
||||||
notify({unregistered, ConnPid}).
|
true = do_unregister_channel(Chan),
|
||||||
|
cast({unregistered, Chan}).
|
||||||
|
|
||||||
do_unregister_connection(Conn) ->
|
%% @private
|
||||||
true = ets:delete(?CONN_STATS_TAB, Conn),
|
do_unregister_channel(Chan) ->
|
||||||
true = ets:delete(?CONN_ATTRS_TAB, Conn),
|
ok = emqx_cm_registry:unregister_channel(Chan),
|
||||||
true = ets:delete_object(?CONN_TAB, Conn).
|
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
|
%% @doc Get conn attrs.
|
||||||
-spec(get_conn_attrs(emqx_types:client_id()) -> list()).
|
-spec(get_conn_attrs(emqx_types:client_id()) -> maybe(attrs())).
|
||||||
get_conn_attrs(ClientId) when is_binary(ClientId) ->
|
get_conn_attrs(ClientId) ->
|
||||||
ConnPid = lookup_conn_pid(ClientId),
|
with_channel(ClientId, fun(ChanPid) ->
|
||||||
get_conn_attrs(ClientId, ConnPid).
|
get_conn_attrs(ClientId, ChanPid)
|
||||||
|
end).
|
||||||
|
|
||||||
-spec(get_conn_attrs(emqx_types:client_id(), pid()) -> list()).
|
-spec(get_conn_attrs(emqx_types:client_id(), chan_pid()) -> maybe(attrs())).
|
||||||
get_conn_attrs(ClientId, ConnPid) when is_binary(ClientId) ->
|
get_conn_attrs(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
emqx_tables:lookup_value(?CONN_ATTRS_TAB, {ClientId, ConnPid}, []).
|
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
|
%% @doc Set conn attrs.
|
||||||
-spec(set_conn_attrs(emqx_types:client_id(), list()) -> true).
|
-spec(set_conn_attrs(emqx_types:client_id(), attrs()) -> ok).
|
||||||
set_conn_attrs(ClientId, Attrs) when is_binary(ClientId) ->
|
set_conn_attrs(ClientId, Attrs) when is_map(Attrs) ->
|
||||||
set_conn_attrs(ClientId, self(), 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).
|
%% @doc Get conn stats.
|
||||||
set_conn_attrs(ClientId, ConnPid, Attrs) when is_binary(ClientId), is_pid(ConnPid) ->
|
-spec(get_conn_stats(emqx_types:client_id()) -> maybe(stats())).
|
||||||
Conn = {ClientId, ConnPid},
|
get_conn_stats(ClientId) ->
|
||||||
ets:insert(?CONN_ATTRS_TAB, {Conn, Attrs}).
|
with_channel(ClientId, fun(ChanPid) ->
|
||||||
|
get_conn_stats(ClientId, ChanPid)
|
||||||
|
end).
|
||||||
|
|
||||||
%% @doc Get conn stats
|
-spec(get_conn_stats(emqx_types:client_id(), chan_pid()) -> maybe(stats())).
|
||||||
-spec(get_conn_stats(emqx_types:client_id()) -> list(emqx_stats:stats())).
|
get_conn_stats(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
get_conn_stats(ClientId) when is_binary(ClientId) ->
|
Chan = {ClientId, ChanPid},
|
||||||
ConnPid = lookup_conn_pid(ClientId),
|
try ets:lookup_element(?CONN_TAB, Chan, 3) of
|
||||||
get_conn_stats(ClientId, ConnPid).
|
Stats -> Stats
|
||||||
|
catch
|
||||||
-spec(get_conn_stats(emqx_types:client_id(), pid()) -> list(emqx_stats:stats())).
|
error:badarg -> undefined
|
||||||
get_conn_stats(ClientId, ConnPid) when is_binary(ClientId) ->
|
end;
|
||||||
Conn = {ClientId, ConnPid},
|
get_conn_stats(ClientId, ChanPid) ->
|
||||||
emqx_tables:lookup_value(?CONN_STATS_TAB, Conn, []).
|
rpc_call(node(ChanPid), get_conn_stats, [ClientId, ChanPid]).
|
||||||
|
|
||||||
%% @doc Set conn stats.
|
%% @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, Stats) when is_binary(ClientId) ->
|
||||||
set_conn_stats(ClientId, self(), Stats).
|
set_conn_stats(ClientId, self(), Stats).
|
||||||
|
|
||||||
-spec(set_conn_stats(emqx_types:client_id(), pid(), list(emqx_stats:stats())) -> true).
|
-spec(set_conn_stats(emqx_types:client_id(), chan_pid(), stats()) -> ok).
|
||||||
set_conn_stats(ClientId, ConnPid, Stats) when is_binary(ClientId), is_pid(ConnPid) ->
|
set_conn_stats(ClientId, ChanPid, Stats) ->
|
||||||
Conn = {ClientId, ConnPid},
|
Chan = {ClientId, ChanPid},
|
||||||
ets:insert(?CONN_STATS_TAB, {Conn, Stats}).
|
_ = ets:update_element(?CONN_TAB, Chan, {3, Stats}),
|
||||||
|
ok.
|
||||||
|
|
||||||
%% @doc Lookup connection pid.
|
%% @doc Open a session.
|
||||||
-spec(lookup_conn_pid(emqx_types:client_id()) -> maybe(pid())).
|
-spec(open_session(map()) -> {ok, emqx_session:session()}
|
||||||
lookup_conn_pid(ClientId) when is_binary(ClientId) ->
|
| {error, Reason :: term()}).
|
||||||
emqx_tables:lookup_value(?CONN_TAB, ClientId).
|
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) ->
|
open_session(Attrs = #{clean_start := false,
|
||||||
gen_server:cast(?CM, {notify, Msg}).
|
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
|
%% gen_server callbacks
|
||||||
%%-----------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
TabOpts = [public, set, {write_concurrency, true}],
|
TabOpts = [public, {write_concurrency, true}],
|
||||||
ok = emqx_tables:new(?CONN_TAB, [{read_concurrency, true} | TabOpts]),
|
ok = emqx_tables:new(?CHAN_TAB, [bag, {read_concurrency, true} | TabOpts]),
|
||||||
ok = emqx_tables:new(?CONN_ATTRS_TAB, TabOpts),
|
ok = emqx_tables:new(?CONN_TAB, [set, compressed | TabOpts]),
|
||||||
ok = emqx_tables:new(?CONN_STATS_TAB, TabOpts),
|
ok = emqx_tables:new(?SESSION_TAB, [set, compressed | TabOpts]),
|
||||||
ok = emqx_stats:update_interval(conn_stats, fun ?MODULE:stats_fun/0),
|
ok = emqx_tables:new(?SESSION_P_TAB, [bag | TabOpts]),
|
||||||
{ok, #{conn_pmon => emqx_pmon:new()}}.
|
ok = emqx_stats:update_interval(chan_stats, fun ?MODULE:stats_fun/0),
|
||||||
|
{ok, #{chan_pmon => emqx_pmon:new()}}.
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
?LOG(error, "[CM] Unexpected call: ~p", [Req]),
|
?LOG(error, "[CM] Unexpected call: ~p", [Req]),
|
||||||
{reply, ignored, State}.
|
{reply, ignored, State}.
|
||||||
|
|
||||||
handle_cast({notify, {registered, ClientId, ConnPid}}, State = #{conn_pmon := PMon}) ->
|
handle_cast({registered, {ClientId, ChanPid}}, State = #{chan_pmon := PMon}) ->
|
||||||
{noreply, State#{conn_pmon := emqx_pmon:monitor(ConnPid, ClientId, PMon)}};
|
PMon1 = emqx_pmon:monitor(ChanPid, ClientId, PMon),
|
||||||
|
{noreply, State#{chan_pmon := PMon1}};
|
||||||
|
|
||||||
handle_cast({notify, {unregistered, ConnPid}}, State = #{conn_pmon := PMon}) ->
|
handle_cast({unregistered, {_ClientId, ChanPid}}, State = #{chan_pmon := PMon}) ->
|
||||||
{noreply, State#{conn_pmon := emqx_pmon:demonitor(ConnPid, PMon)}};
|
PMon1 = emqx_pmon:demonitor(ChanPid, PMon),
|
||||||
|
{noreply, State#{chan_pmon := PMon1}};
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
?LOG(error, "[CM] Unexpected cast: ~p", [Msg]),
|
?LOG(error, "[CM] Unexpected cast: ~p", [Msg]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{conn_pmon := PMon}) ->
|
handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) ->
|
||||||
ConnPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
||||||
{Items, PMon1} = emqx_pmon:erase_all(ConnPids, PMon),
|
{Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
|
||||||
ok = emqx_pool:async_submit(
|
ok = emqx_pool:async_submit(fun lists:foreach/2, [fun clean_down/1, Items]),
|
||||||
fun lists:foreach/2, [fun clean_down/1, Items]),
|
{noreply, State#{chan_pmon := PMon1}};
|
||||||
{noreply, State#{conn_pmon := PMon1}};
|
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
?LOG(error, "[CM] Unexpected info: ~p", [Info]),
|
?LOG(error, "[CM] Unexpected info: ~p", [Info]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
emqx_stats:cancel_update(conn_stats).
|
emqx_stats:cancel_update(chan_stats).
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
clean_down({Pid, ClientId}) ->
|
clean_down({ChanPid, ClientId}) ->
|
||||||
Conn = {ClientId, Pid},
|
Chan = {ClientId, ChanPid},
|
||||||
case ets:member(?CONN_TAB, ClientId)
|
do_unregister_channel(Chan).
|
||||||
orelse ets:member(?CONN_ATTRS_TAB, Conn) of
|
|
||||||
true ->
|
|
||||||
do_unregister_connection(Conn);
|
|
||||||
false -> false
|
|
||||||
end.
|
|
||||||
|
|
||||||
stats_fun() ->
|
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;
|
undefined -> ok;
|
||||||
Size -> emqx_stats:setstat('connections.count', 'connections.max', Size)
|
Size -> emqx_stats:setstat(Stat, MaxStat, Size)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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.
|
||||||
|
%% 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_cm_locker).
|
||||||
|
|
||||||
|
-include("emqx.hrl").
|
||||||
|
-include("types.hrl").
|
||||||
|
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
-export([ trans/2
|
||||||
|
, trans/3
|
||||||
|
, lock/1
|
||||||
|
, lock/2
|
||||||
|
, unlock/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-spec(start_link() -> startlink_ret()).
|
||||||
|
start_link() ->
|
||||||
|
ekka_locker:start_link(?MODULE).
|
||||||
|
|
||||||
|
-spec(trans(emqx_types:client_id(), fun(([node()]) -> any())) -> any()).
|
||||||
|
trans(ClientId, Fun) ->
|
||||||
|
trans(ClientId, Fun, undefined).
|
||||||
|
|
||||||
|
-spec(trans(maybe(emqx_types:client_id()),
|
||||||
|
fun(([node()])-> any()), ekka_locker:piggyback()) -> any()).
|
||||||
|
trans(undefined, Fun, _Piggyback) ->
|
||||||
|
Fun([]);
|
||||||
|
trans(ClientId, Fun, Piggyback) ->
|
||||||
|
case lock(ClientId, Piggyback) of
|
||||||
|
{true, Nodes} ->
|
||||||
|
try Fun(Nodes) after unlock(ClientId) end;
|
||||||
|
{false, _Nodes} ->
|
||||||
|
{error, client_id_unavailable}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec(lock(emqx_types:client_id()) -> ekka_locker:lock_result()).
|
||||||
|
lock(ClientId) ->
|
||||||
|
ekka_locker:acquire(?MODULE, ClientId, strategy()).
|
||||||
|
|
||||||
|
-spec(lock(emqx_types:client_id(), ekka_locker:piggyback()) -> ekka_locker:lock_result()).
|
||||||
|
lock(ClientId, Piggyback) ->
|
||||||
|
ekka_locker:acquire(?MODULE, ClientId, strategy(), Piggyback).
|
||||||
|
|
||||||
|
-spec(unlock(emqx_types:client_id()) -> {boolean(), [node()]}).
|
||||||
|
unlock(ClientId) ->
|
||||||
|
ekka_locker:release(?MODULE, ClientId, strategy()).
|
||||||
|
|
||||||
|
-spec(strategy() -> local | one | quorum | all).
|
||||||
|
strategy() ->
|
||||||
|
emqx_config:get_env(session_locking_strategy, quorum).
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_sm_registry).
|
%% Global Channel Registry
|
||||||
|
-module(emqx_cm_registry).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
@ -22,12 +25,14 @@
|
||||||
|
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
|
||||||
-export([ is_enabled/0
|
-export([is_enabled/0]).
|
||||||
, register_session/1
|
|
||||||
, lookup_session/1
|
-export([ register_channel/1
|
||||||
, unregister_session/1
|
, unregister_channel/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([lookup_channels/1]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([ init/1
|
-export([ init/1
|
||||||
, handle_call/3
|
, handle_call/3
|
||||||
|
@ -38,57 +43,67 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(REGISTRY, ?MODULE).
|
-define(REGISTRY, ?MODULE).
|
||||||
-define(TAB, emqx_session_registry).
|
-define(TAB, emqx_channel_registry).
|
||||||
-define(LOCK, {?MODULE, cleanup_sessions}).
|
-define(LOCK, {?MODULE, cleanup_down}).
|
||||||
|
|
||||||
-record(global_session, {sid, pid}).
|
-record(channel, {chid, pid}).
|
||||||
|
|
||||||
-type(session_pid() :: pid()).
|
%% @doc Start the global channel registry.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% APIs
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Start the global session manager.
|
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []).
|
gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% APIs
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Is the global registry enabled?
|
||||||
-spec(is_enabled() -> boolean()).
|
-spec(is_enabled() -> boolean()).
|
||||||
is_enabled() ->
|
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())).
|
%% @doc Register a global channel.
|
||||||
lookup_session(ClientId) ->
|
-spec(register_channel(emqx_types:client_id()
|
||||||
[SessPid || #global_session{pid = SessPid} <- mnesia:dirty_read(?TAB, ClientId)].
|
| {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_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
|
||||||
register_session({ClientId, SessPid}) when is_binary(ClientId), is_pid(SessPid) ->
|
|
||||||
case is_enabled() of
|
case is_enabled() of
|
||||||
true -> mnesia:dirty_write(?TAB, record(ClientId, SessPid));
|
true -> mnesia:dirty_write(?TAB, record(ClientId, ChanPid));
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec(unregister_session({emqx_types:client_id(), session_pid()}) -> ok).
|
%% @doc Unregister a global channel.
|
||||||
unregister_session({ClientId, SessPid}) when is_binary(ClientId), is_pid(SessPid) ->
|
-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
|
case is_enabled() of
|
||||||
true -> mnesia:dirty_delete_object(?TAB, record(ClientId, SessPid));
|
true -> mnesia:dirty_delete_object(?TAB, record(ClientId, ChanPid));
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
record(ClientId, SessPid) ->
|
%% @doc Lookup the global channels.
|
||||||
#global_session{sid = ClientId, pid = SessPid}.
|
-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
|
%% gen_server callbacks
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
ok = ekka_mnesia:create_table(?TAB, [
|
ok = ekka_mnesia:create_table(?TAB, [
|
||||||
{type, bag},
|
{type, bag},
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{record_name, global_session},
|
{record_name, channel},
|
||||||
{attributes, record_info(fields, global_session)},
|
{attributes, record_info(fields, channel)},
|
||||||
{storage_properties, [{ets, [{read_concurrency, true},
|
{storage_properties, [{ets, [{read_concurrency, true},
|
||||||
{write_concurrency, true}]}]}]),
|
{write_concurrency, true}]}]}]),
|
||||||
ok = ekka_mnesia:copy_table(?TAB),
|
ok = ekka_mnesia:copy_table(?TAB),
|
||||||
|
@ -106,7 +121,7 @@ handle_cast(Msg, State) ->
|
||||||
handle_info({membership, {mnesia, down, Node}}, State) ->
|
handle_info({membership, {mnesia, down, Node}}, State) ->
|
||||||
global:trans({?LOCK, self()},
|
global:trans({?LOCK, self()},
|
||||||
fun() ->
|
fun() ->
|
||||||
mnesia:transaction(fun cleanup_sessions/1, [Node])
|
mnesia:transaction(fun cleanup_channels/1, [Node])
|
||||||
end),
|
end),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
|
@ -123,14 +138,14 @@ terminate(_Reason, _State) ->
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
cleanup_sessions(Node) ->
|
cleanup_channels(Node) ->
|
||||||
Pat = [{#global_session{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}],
|
Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}],
|
||||||
lists:foreach(fun delete_session/1, mnesia:select(?TAB, Pat, write)).
|
lists:foreach(fun delete_channel/1, mnesia:select(?TAB, Pat, write)).
|
||||||
|
|
||||||
delete_session(Session) ->
|
delete_channel(Chan) ->
|
||||||
mnesia:delete_object(?TAB, Session, write).
|
mnesia:delete_object(?TAB, Chan, write).
|
||||||
|
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_sm_sup).
|
-module(emqx_cm_sup).
|
||||||
|
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
|
@ -24,41 +26,45 @@ start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
%% Session locker
|
Banned = #{id => banned,
|
||||||
|
start => {emqx_banned, start_link, []},
|
||||||
|
restart => permanent,
|
||||||
|
shutdown => 1000,
|
||||||
|
type => worker,
|
||||||
|
modules => [emqx_banned]},
|
||||||
|
Flapping = #{id => flapping,
|
||||||
|
start => {emqx_flapping, start_link, []},
|
||||||
|
restart => permanent,
|
||||||
|
shutdown => 1000,
|
||||||
|
type => worker,
|
||||||
|
modules => [emqx_flapping]},
|
||||||
|
%% Channel locker
|
||||||
Locker = #{id => locker,
|
Locker = #{id => locker,
|
||||||
start => {emqx_sm_locker, start_link, []},
|
start => {emqx_cm_locker, start_link, []},
|
||||||
restart => permanent,
|
restart => permanent,
|
||||||
shutdown => 5000,
|
shutdown => 5000,
|
||||||
type => worker,
|
type => worker,
|
||||||
modules => [emqx_sm_locker]
|
modules => [emqx_cm_locker]
|
||||||
},
|
},
|
||||||
%% Session registry
|
%% Channel registry
|
||||||
Registry = #{id => registry,
|
Registry = #{id => registry,
|
||||||
start => {emqx_sm_registry, start_link, []},
|
start => {emqx_cm_registry, start_link, []},
|
||||||
restart => permanent,
|
restart => permanent,
|
||||||
shutdown => 5000,
|
shutdown => 5000,
|
||||||
type => worker,
|
type => worker,
|
||||||
modules => [emqx_sm_registry]
|
modules => [emqx_cm_registry]
|
||||||
},
|
},
|
||||||
%% Session Manager
|
%% Channel Manager
|
||||||
Manager = #{id => manager,
|
Manager = #{id => manager,
|
||||||
start => {emqx_sm, start_link, []},
|
start => {emqx_cm, start_link, []},
|
||||||
restart => permanent,
|
restart => permanent,
|
||||||
shutdown => 5000,
|
shutdown => 5000,
|
||||||
type => worker,
|
type => worker,
|
||||||
modules => [emqx_sm]
|
modules => [emqx_cm]
|
||||||
},
|
},
|
||||||
%% Session Sup
|
SupFlags = #{strategy => one_for_one,
|
||||||
SessSpec = #{start => {emqx_session, start_link, []},
|
intensity => 100,
|
||||||
shutdown => brutal_kill,
|
period => 10
|
||||||
clean_down => fun emqx_sm:clean_down/1
|
|
||||||
},
|
},
|
||||||
SessionSup = #{id => session_sup,
|
{ok, {SupFlags, [Banned, Flapping, Locker, Registry, Manager]}}.
|
||||||
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]}}.
|
|
||||||
|
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc This module is used to garbage clean the flapping records.
|
||||||
|
|
||||||
-module(emqx_flapping).
|
-module(emqx_flapping).
|
||||||
|
|
||||||
|
@ -19,31 +23,29 @@
|
||||||
|
|
||||||
-behaviour(gen_statem).
|
-behaviour(gen_statem).
|
||||||
|
|
||||||
-export([start_link/1]).
|
-export([start_link/0]).
|
||||||
|
|
||||||
%% This module is used to garbage clean the flapping records
|
|
||||||
|
|
||||||
%% gen_statem callbacks
|
%% gen_statem callbacks
|
||||||
-export([ terminate/3
|
-export([ init/1
|
||||||
, code_change/4
|
|
||||||
, init/1
|
|
||||||
, initialized/3
|
, initialized/3
|
||||||
, callback_mode/0
|
, callback_mode/0
|
||||||
|
, terminate/3
|
||||||
|
, code_change/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(FLAPPING_TAB, ?MODULE).
|
-define(FLAPPING_TAB, ?MODULE).
|
||||||
|
|
||||||
-export([check/3]).
|
-export([check/3]).
|
||||||
|
|
||||||
-record(flapping,
|
-record(flapping, {
|
||||||
{ client_id :: binary()
|
client_id :: binary(),
|
||||||
, check_count :: integer()
|
check_count :: integer(),
|
||||||
, timestamp :: integer()
|
timestamp :: integer()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type(flapping_record() :: #flapping{}).
|
-type(flapping_record() :: #flapping{}).
|
||||||
-type(flapping_state() :: flapping | ok).
|
|
||||||
|
|
||||||
|
-type(flapping_state() :: flapping | ok).
|
||||||
|
|
||||||
%% @doc This function is used to initialize flapping records
|
%% @doc This function is used to initialize flapping records
|
||||||
%% the expiry time unit is minutes.
|
%% the expiry time unit is minutes.
|
||||||
|
@ -96,18 +98,20 @@ check_flapping(Action, CheckCount, _Threshold = {TimesThreshold, TimeInterval},
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_statem callbacks
|
%% 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
|
TabOpts = [ public
|
||||||
, set
|
, set
|
||||||
, {keypos, 2}
|
, {keypos, 2}
|
||||||
, {write_concurrency, true}
|
, {write_concurrency, true}
|
||||||
, {read_concurrency, true}],
|
, {read_concurrency, true}],
|
||||||
ok = emqx_tables:new(?FLAPPING_TAB, TabOpts),
|
ok = emqx_tables:new(?FLAPPING_TAB, TabOpts),
|
||||||
{ok, initialized, #{timer_interval => TimerInterval}}.
|
{ok, initialized, #{timer_interval => Interval}}.
|
||||||
|
|
||||||
callback_mode() -> [state_functions, state_enter].
|
callback_mode() -> [state_functions, state_enter].
|
||||||
|
|
||||||
|
@ -134,3 +138,4 @@ clean_expired_records() ->
|
||||||
NowTime = emqx_time:now_secs(),
|
NowTime = emqx_time:now_secs(),
|
||||||
MatchSpec = [{{'$1', '$2', '$3'},[{'<', '$3', NowTime}], [true]}],
|
MatchSpec = [{{'$1', '$2', '$3'},[{'<', '$3', NowTime}], [true]}],
|
||||||
ets:select_delete(?FLAPPING_TAB, MatchSpec).
|
ets:select_delete(?FLAPPING_TAB, MatchSpec).
|
||||||
|
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_hooks).
|
-module(emqx_hooks).
|
||||||
|
|
||||||
|
@ -19,7 +21,9 @@
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
|
|
||||||
-export([start_link/0, stop/0]).
|
-export([ start_link/0
|
||||||
|
, stop/0
|
||||||
|
]).
|
||||||
|
|
||||||
%% Hooks API
|
%% Hooks API
|
||||||
-export([ add/2
|
-export([ add/2
|
||||||
|
@ -52,11 +56,16 @@
|
||||||
-type(action() :: function() | mfa()).
|
-type(action() :: function() | mfa()).
|
||||||
-type(filter() :: function() | mfa()).
|
-type(filter() :: function() | mfa()).
|
||||||
|
|
||||||
-record(callback, {action :: action(),
|
-record(callback, {
|
||||||
|
action :: action(),
|
||||||
filter :: filter(),
|
filter :: filter(),
|
||||||
priority :: integer()}).
|
priority :: integer()
|
||||||
|
}).
|
||||||
|
|
||||||
-record(hook, {name :: hookpoint(), callbacks :: list(#callback{})}).
|
-record(hook, {
|
||||||
|
name :: hookpoint(),
|
||||||
|
callbacks :: list(#callback{})
|
||||||
|
}).
|
||||||
|
|
||||||
-export_type([hookpoint/0, action/0, filter/0]).
|
-export_type([hookpoint/0, action/0, filter/0]).
|
||||||
|
|
||||||
|
@ -65,15 +74,16 @@
|
||||||
|
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
start_link() ->
|
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).
|
-spec(stop() -> ok).
|
||||||
stop() ->
|
stop() ->
|
||||||
gen_server:stop(?SERVER, normal, infinity).
|
gen_server:stop(?SERVER, normal, infinity).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Hooks API
|
%% Hooks API
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Register a callback
|
%% @doc Register a callback
|
||||||
-spec(add(hookpoint(), action() | #callback{}) -> ok_or_error(already_exists)).
|
-spec(add(hookpoint(), action() | #callback{}) -> ok_or_error(already_exists)).
|
||||||
|
@ -111,7 +121,6 @@ run(HookPoint, Args) ->
|
||||||
run_fold(HookPoint, Args, Acc) ->
|
run_fold(HookPoint, Args, Acc) ->
|
||||||
do_run_fold(lookup(HookPoint), Args, Acc).
|
do_run_fold(lookup(HookPoint), Args, Acc).
|
||||||
|
|
||||||
|
|
||||||
do_run([#callback{action = Action, filter = Filter} | Callbacks], Args) ->
|
do_run([#callback{action = Action, filter = Filter} | Callbacks], Args) ->
|
||||||
case filter_passed(Filter, Args) andalso execute(Action, Args) of
|
case filter_passed(Filter, Args) andalso execute(Action, Args) of
|
||||||
%% stop the hook chain and return
|
%% stop the hook chain and return
|
||||||
|
@ -163,12 +172,12 @@ lookup(HookPoint) ->
|
||||||
[] -> []
|
[] -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
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, #{}}.
|
{ok, #{}}.
|
||||||
|
|
||||||
handle_call({add, HookPoint, Callback = #callback{action = Action}}, _From, State) ->
|
handle_call({add, HookPoint, Callback = #callback{action = Action}}, _From, State) ->
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_keepalive).
|
-module(emqx_keepalive).
|
||||||
|
|
||||||
|
@ -20,15 +22,22 @@
|
||||||
, cancel/1
|
, cancel/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}).
|
-record(keepalive, {
|
||||||
|
statfun,
|
||||||
|
statval,
|
||||||
|
tsec,
|
||||||
|
tmsg,
|
||||||
|
tref,
|
||||||
|
repeat = 0
|
||||||
|
}).
|
||||||
|
|
||||||
-opaque(keepalive() :: #keepalive{}).
|
-opaque(keepalive() :: #keepalive{}).
|
||||||
|
|
||||||
-export_type([keepalive/0]).
|
-export_type([keepalive/0]).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Start a keepalive
|
%% @doc Start a keepalive
|
||||||
-spec(start(fun(), integer(), any()) -> {ok, keepalive()} | {error, term()}).
|
-spec(start(fun(), integer(), any()) -> {ok, keepalive()} | {error, term()}).
|
||||||
|
@ -79,3 +88,4 @@ cancel(_) ->
|
||||||
|
|
||||||
timer(Secs, Msg) ->
|
timer(Secs, Msg) ->
|
||||||
erlang:send_after(timer:seconds(Secs), self(), Msg).
|
erlang:send_after(timer:seconds(Secs), self(), Msg).
|
||||||
|
|
||||||
|
|
|
@ -34,18 +34,22 @@
|
||||||
-define(IS_STRING(String),
|
-define(IS_STRING(String),
|
||||||
(is_list(String) orelse is_binary(String))).
|
(is_list(String) orelse is_binary(String))).
|
||||||
|
|
||||||
%%%-----------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%%% Types
|
%% Types
|
||||||
-type config() :: #{chars_limit => pos_integer() | unlimited,
|
|
||||||
|
-type(config() :: #{chars_limit => pos_integer() | unlimited,
|
||||||
depth => pos_integer() | unlimited,
|
depth => pos_integer() | unlimited,
|
||||||
max_size => pos_integer() | unlimited,
|
max_size => pos_integer() | unlimited,
|
||||||
report_cb => logger:report_cb(),
|
report_cb => logger:report_cb(),
|
||||||
quit => template()}.
|
quit => template()}).
|
||||||
-type template() :: [metakey() | {metakey(),template(),template()} | string()].
|
|
||||||
-type metakey() :: atom() | [atom()].
|
-type(template() :: [metakey() | {metakey(),template(),template()} | string()]).
|
||||||
|
|
||||||
|
-type(metakey() :: atom() | [atom()]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
|
||||||
%%%-----------------------------------------------------------------
|
|
||||||
%%% API
|
|
||||||
-spec format(LogEvent,Config) -> unicode:chardata() when
|
-spec format(LogEvent,Config) -> unicode:chardata() when
|
||||||
LogEvent :: logger:log_event(),
|
LogEvent :: logger:log_event(),
|
||||||
Config :: config().
|
Config :: config().
|
||||||
|
|
|
@ -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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with 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.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_modules).
|
-module(emqx_modules).
|
||||||
|
|
||||||
|
@ -20,20 +22,25 @@
|
||||||
, unload/0
|
, unload/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
%% @doc Load all the extended modules.
|
||||||
-spec(load() -> ok).
|
-spec(load() -> ok).
|
||||||
load() ->
|
load() ->
|
||||||
ok = emqx_mod_acl_internal:load([]),
|
ok = emqx_mod_acl_internal:load([]),
|
||||||
lists:foreach(
|
lists:foreach(fun load/1, modules()).
|
||||||
fun({Mod, Env}) ->
|
|
||||||
ok = Mod:load(Env),
|
|
||||||
?LOG(info, "[Modules] Load ~s module successfully.", [Mod])
|
|
||||||
end, emqx_config:get_env(modules, [])).
|
|
||||||
|
|
||||||
|
load({Mod, Env}) ->
|
||||||
|
ok = Mod:load(Env),
|
||||||
|
?LOG(info, "[Modules] Load ~s module successfully.", [Mod]).
|
||||||
|
|
||||||
|
modules() ->
|
||||||
|
emqx_config:get_env(modules, []).
|
||||||
|
|
||||||
|
%% @doc Unload all the extended modules.
|
||||||
-spec(unload() -> ok).
|
-spec(unload() -> ok).
|
||||||
unload() ->
|
unload() ->
|
||||||
ok = emqx_mod_acl_internal:unload([]),
|
ok = emqx_mod_acl_internal:unload([]),
|
||||||
lists:foreach(
|
lists:foreach(fun unload/1, modules()).
|
||||||
fun({Mod, Env}) ->
|
|
||||||
Mod:unload(Env) end,
|
unload({Mod, Env}) ->
|
||||||
emqx_config:get_env(modules, [])).
|
Mod:unload(Env).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue