|
|
@@ -21,12 +21,15 @@
|
|
|
-export([start_link/0]).
|
|
|
|
|
|
-export([open_session/1, close_session/1]).
|
|
|
--export([lookup_session/1, lookup_session_pid/1]).
|
|
|
-export([resume_session/2]).
|
|
|
-export([discard_session/1, discard_session/2]).
|
|
|
--export([register_session/2, unregister_session/1]).
|
|
|
--export([get_session_attrs/1, set_session_attrs/2]).
|
|
|
--export([get_session_stats/1, set_session_stats/2]).
|
|
|
+-export([register_session/1, register_session/2]).
|
|
|
+-export([unregister_session/1, unregister_session/2]).
|
|
|
+-export([get_session_attrs/1, get_session_attrs/2,
|
|
|
+ set_session_attrs/2, set_session_attrs/3]).
|
|
|
+-export([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]).
|
|
|
@@ -46,6 +49,8 @@
|
|
|
-define(SESSION_ATTRS_TAB, emqx_session_attrs).
|
|
|
-define(SESSION_STATS_TAB, emqx_session_stats).
|
|
|
|
|
|
+-define(BATCH_SIZE, 10000).
|
|
|
+
|
|
|
-spec(start_link() -> emqx_types:startlink_ret()).
|
|
|
start_link() ->
|
|
|
gen_server:start_link({local, ?SM}, ?MODULE, [], []).
|
|
|
@@ -62,8 +67,8 @@ open_session(SessAttrs = #{clean_start := true, client_id := ClientId, conn_pid
|
|
|
open_session(SessAttrs = #{clean_start := false, client_id := ClientId}) ->
|
|
|
ResumeStart = fun(_) ->
|
|
|
case resume_session(ClientId, SessAttrs) of
|
|
|
- {ok, SPid} ->
|
|
|
- {ok, SPid, true};
|
|
|
+ {ok, SessPid} ->
|
|
|
+ {ok, SessPid, true};
|
|
|
{error, not_found} ->
|
|
|
emqx_session:start_link(SessAttrs)
|
|
|
end
|
|
|
@@ -75,76 +80,68 @@ open_session(SessAttrs = #{clean_start := false, client_id := ClientId}) ->
|
|
|
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({_ClientId, SPid}) ->
|
|
|
- case catch emqx_session:discard(SPid, ConnPid) of
|
|
|
- {Err, Reason} when Err =:= 'EXIT'; Err =:= error ->
|
|
|
- emqx_logger:error("[SM] Failed to discard ~p: ~p", [SPid, Reason]);
|
|
|
- ok -> ok
|
|
|
- end
|
|
|
- end, lookup_session(ClientId)).
|
|
|
+ fun(SessPid) ->
|
|
|
+ try emqx_session:discard(SessPid, ConnPid)
|
|
|
+ catch
|
|
|
+ _:Error:_Stk ->
|
|
|
+ emqx_logger:error("[SM] 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(ClientId) of
|
|
|
+ case lookup_session_pids(ClientId) of
|
|
|
[] -> {error, not_found};
|
|
|
- [{_ClientId, SPid}] ->
|
|
|
- ok = emqx_session:resume(SPid, SessAttrs),
|
|
|
- {ok, SPid};
|
|
|
- Sessions ->
|
|
|
- [{_, SPid}|StaleSessions] = lists:reverse(Sessions),
|
|
|
- emqx_logger:error("[SM] More than one session found: ~p", [Sessions]),
|
|
|
- lists:foreach(fun({_, StalePid}) ->
|
|
|
+ [SessPid] ->
|
|
|
+ ok = emqx_session:resume(SessPid, SessAttrs),
|
|
|
+ {ok, SessPid};
|
|
|
+ SessPids ->
|
|
|
+ [SessPid|StalePids] = lists:reverse(SessPids),
|
|
|
+ emqx_logger:error("[SM] More than one session found: ~p", [SessPids]),
|
|
|
+ lists:foreach(fun(StalePid) ->
|
|
|
catch emqx_session:discard(StalePid, ConnPid)
|
|
|
- end, StaleSessions),
|
|
|
- ok = emqx_session:resume(SPid, SessAttrs),
|
|
|
- {ok, SPid}
|
|
|
+ end, StalePids),
|
|
|
+ ok = emqx_session:resume(SessPid, SessAttrs),
|
|
|
+ {ok, SessPid}
|
|
|
end.
|
|
|
|
|
|
%% @doc Close a session.
|
|
|
--spec(close_session({emqx_types:client_id(), pid()} | pid()) -> ok).
|
|
|
-close_session({_ClientId, SPid}) ->
|
|
|
- emqx_session:close(SPid);
|
|
|
-close_session(SPid) when is_pid(SPid) ->
|
|
|
- emqx_session:close(SPid).
|
|
|
-
|
|
|
-%% @doc Register a session with attributes.
|
|
|
--spec(register_session(emqx_types:client_id() | {emqx_types:client_id(), pid()},
|
|
|
- list(emqx_session:attr())) -> ok).
|
|
|
-register_session(ClientId, SessAttrs) when is_binary(ClientId) ->
|
|
|
- register_session({ClientId, self()}, SessAttrs);
|
|
|
-
|
|
|
-register_session(Session = {ClientId, SPid}, SessAttrs) when is_binary(ClientId), is_pid(SPid) ->
|
|
|
- true = ets:insert(?SESSION_TAB, Session),
|
|
|
- true = ets:insert(?SESSION_ATTRS_TAB, {Session, SessAttrs}),
|
|
|
- true = proplists:get_value(clean_start, SessAttrs, true)
|
|
|
- orelse ets:insert(?SESSION_P_TAB, Session),
|
|
|
- ok = emqx_sm_registry:register_session(Session),
|
|
|
- notify({registered, ClientId, SPid}).
|
|
|
+-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;
|
|
|
|
|
|
-%% @doc Get session attrs
|
|
|
--spec(get_session_attrs({emqx_types:client_id(), pid()}) -> list(emqx_session:attr())).
|
|
|
-get_session_attrs(Session = {ClientId, SPid}) when is_binary(ClientId), is_pid(SPid) ->
|
|
|
- emqx_tables:lookup_value(?SESSION_ATTRS_TAB, Session, []).
|
|
|
+close_session(SessPid) when is_pid(SessPid) ->
|
|
|
+ emqx_session:close(SessPid).
|
|
|
|
|
|
-%% @doc Set session attrs
|
|
|
--spec(set_session_attrs(emqx_types:client_id() | {emqx_types:client_id(), pid()},
|
|
|
- list(emqx_session:attr())) -> true).
|
|
|
-set_session_attrs(ClientId, SessAttrs) when is_binary(ClientId) ->
|
|
|
- set_session_attrs({ClientId, self()}, SessAttrs);
|
|
|
-set_session_attrs(Session = {ClientId, SPid}, SessAttrs) when is_binary(ClientId), is_pid(SPid) ->
|
|
|
- ets:insert(?SESSION_ATTRS_TAB, {Session, SessAttrs}).
|
|
|
+%% @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),
|
|
|
+ ok = emqx_sm_registry:register_session(Session),
|
|
|
+ notify({registered, ClientId, SessPid}).
|
|
|
|
|
|
%% @doc Unregister a session
|
|
|
--spec(unregister_session(emqx_types:client_id() | {emqx_types:client_id(), pid()}) -> ok).
|
|
|
+-spec(unregister_session(emqx_types:client_id()) -> ok).
|
|
|
unregister_session(ClientId) when is_binary(ClientId) ->
|
|
|
- unregister_session({ClientId, self()});
|
|
|
+ unregister_session(ClientId, self()).
|
|
|
|
|
|
-unregister_session(Session = {ClientId, SPid}) when is_binary(ClientId), is_pid(SPid) ->
|
|
|
- ok = do_unregister_session(Session),
|
|
|
- notify({unregistered, ClientId, SPid}).
|
|
|
+-spec(unregister_session(emqx_types:client_id(), pid()) -> ok).
|
|
|
+unregister_session(ClientId, SessPid) when is_binary(ClientId), is_pid(SessPid) ->
|
|
|
+ ok = do_unregister_session({ClientId, SessPid}),
|
|
|
+ notify({unregistered, SessPid}).
|
|
|
|
|
|
%% @private
|
|
|
do_unregister_session(Session) ->
|
|
|
@@ -154,42 +151,69 @@ do_unregister_session(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(), pid()}) -> list(emqx_stats:stats())).
|
|
|
-get_session_stats(Session = {ClientId, SPid}) when is_binary(ClientId), is_pid(SPid) ->
|
|
|
- emqx_tables:lookup_value(?SESSION_STATS_TAB, Session, []).
|
|
|
+-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_types:client_id(), pid()},
|
|
|
- emqx_stats:stats()) -> true).
|
|
|
+-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);
|
|
|
-set_session_stats(Session = {ClientId, SPid}, Stats) when is_binary(ClientId), is_pid(SPid) ->
|
|
|
- ets:insert(?SESSION_STATS_TAB, {Session, Stats}).
|
|
|
+ set_session_stats(ClientId, self(), Stats).
|
|
|
|
|
|
-%% @doc Lookup a session from registry
|
|
|
--spec(lookup_session(emqx_types:client_id()) -> list({emqx_types:client_id(), pid()})).
|
|
|
-lookup_session(ClientId) ->
|
|
|
+-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 -> ets:lookup(?SESSION_TAB, ClientId)
|
|
|
+ false -> ets:lookup(?SESSION_TAB, ClientId, [])
|
|
|
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_pid(ClientId) of
|
|
|
- Pid when is_pid(Pid) ->
|
|
|
- Pid ! {dispatch, Topic, Msg};
|
|
|
- undefined ->
|
|
|
+ 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.
|
|
|
|
|
|
-%% @doc Lookup session pid.
|
|
|
--spec(lookup_session_pid(emqx_types:client_id()) -> pid() | undefined).
|
|
|
-lookup_session_pid(ClientId) ->
|
|
|
- emqx_tables:lookup_value(?SESSION_TAB, ClientId).
|
|
|
-
|
|
|
notify(Event) ->
|
|
|
gen_server:cast(?SM, {notify, Event}).
|
|
|
|
|
|
@@ -203,43 +227,36 @@ init([]) ->
|
|
|
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(sm_stats, fun ?MODULE:stats_fun/0),
|
|
|
+ ok = emqx_stats:update_interval(sess_stats, fun ?MODULE:stats_fun/0),
|
|
|
{ok, #{sess_pmon => emqx_pmon:new()}}.
|
|
|
|
|
|
handle_call(Req, _From, State) ->
|
|
|
emqx_logger:error("[SM] unexpected call: ~p", [Req]),
|
|
|
{reply, ignored, State}.
|
|
|
|
|
|
-handle_cast({notify, {registered, ClientId, SPid}}, State = #{sess_pmon := PMon}) ->
|
|
|
- {noreply, State#{sess_pmon := emqx_pmon:monitor(SPid, ClientId, PMon)}};
|
|
|
+handle_cast({notify, {registered, ClientId, SessPid}}, State = #{sess_pmon := PMon}) ->
|
|
|
+ {noreply, State#{sess_pmon := emqx_pmon:monitor(SessPid, ClientId, PMon)}};
|
|
|
|
|
|
-handle_cast({notify, {unregistered, _ClientId, SPid}}, State = #{sess_pmon := PMon}) ->
|
|
|
- {noreply, State#{sess_pmon := emqx_pmon:demonitor(SPid, PMon)}};
|
|
|
+handle_cast({notify, {unregistered, SessPid}}, State = #{sess_pmon := PMon}) ->
|
|
|
+ {noreply, State#{sess_pmon := emqx_pmon:demonitor(SessPid, PMon)}};
|
|
|
|
|
|
handle_cast(Msg, State) ->
|
|
|
emqx_logger:error("[SM] unexpected cast: ~p", [Msg]),
|
|
|
{noreply, State}.
|
|
|
|
|
|
-handle_info({'DOWN', _MRef, process, DownPid, _Reason}, State = #{sess_pmon := PMon}) ->
|
|
|
- case emqx_pmon:find(DownPid, PMon) of
|
|
|
- undefined ->
|
|
|
- {noreply, State};
|
|
|
- ClientId ->
|
|
|
- Session = {ClientId, DownPid},
|
|
|
- case ets:member(?SESSION_ATTRS_TAB, Session) of
|
|
|
- true ->
|
|
|
- ok = emqx_pool:async_submit(fun do_unregister_session/1, [Session]);
|
|
|
- false -> ok
|
|
|
- end,
|
|
|
- {noreply, State#{sess_pmon := emqx_pmon:erase(DownPid, PMon)}}
|
|
|
- end;
|
|
|
+handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{sess_pmon := PMon}) ->
|
|
|
+ SessPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
|
|
+ {Items, PMon1} = emqx_pmon:erase_all(SessPids, PMon),
|
|
|
+ ok = emqx_pool:async_submit(
|
|
|
+ fun lists:foreach/2, [fun clean_down/1, Items]),
|
|
|
+ {noreply, State#{sess_pmon := PMon1}};
|
|
|
|
|
|
handle_info(Info, State) ->
|
|
|
emqx_logger:error("[SM] unexpected info: ~p", [Info]),
|
|
|
{noreply, State}.
|
|
|
|
|
|
terminate(_Reason, _State) ->
|
|
|
- emqx_stats:cancel_update(sm_stats).
|
|
|
+ emqx_stats:cancel_update(sess_stats).
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
{ok, State}.
|
|
|
@@ -248,6 +265,15 @@ code_change(_OldVsn, State, _Extra) ->
|
|
|
%% Internal functions
|
|
|
%%------------------------------------------------------------------------------
|
|
|
|
|
|
+clean_down({SessPid, ClientId}) ->
|
|
|
+ Session = {ClientId, SessPid},
|
|
|
+ case ets:member(?SESSION_TAB, ClientId)
|
|
|
+ orelse ets:member(?SESSION_ATTRS_TAB, Session) of
|
|
|
+ true ->
|
|
|
+ do_unregister_session(Session);
|
|
|
+ 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').
|