1 |
|
-module(ejabberd_sm_cets). |
2 |
|
|
3 |
|
-behavior(ejabberd_sm_backend). |
4 |
|
|
5 |
|
-include("mongoose.hrl"). |
6 |
|
-include("session.hrl"). |
7 |
|
|
8 |
|
-export([init/1, |
9 |
|
get_sessions/0, |
10 |
|
get_sessions/1, |
11 |
|
get_sessions/2, |
12 |
|
get_sessions/3, |
13 |
|
set_session/4, |
14 |
|
delete_session/4, |
15 |
|
cleanup/1, |
16 |
|
total_count/0, |
17 |
|
unique_count/0]). |
18 |
|
|
19 |
|
-define(TABLE, cets_session). |
20 |
|
|
21 |
|
-spec init(map()) -> any(). |
22 |
|
init(_Opts) -> |
23 |
33 |
cets:start(?TABLE, #{}), |
24 |
33 |
cets_discovery:add_table(mongoose_cets_discovery, ?TABLE). |
25 |
|
|
26 |
|
-spec get_sessions() -> [ejabberd_sm:session()]. |
27 |
|
get_sessions() -> |
28 |
117 |
tuples_to_sessions(ets:tab2list(?TABLE)). |
29 |
|
|
30 |
|
-spec get_sessions(jid:lserver()) -> [ejabberd_sm:session()]. |
31 |
|
get_sessions(Server) -> |
32 |
|
%% This is not a full table scan. From the ETS docs: |
33 |
|
%% For ordered_set a partially bound key will limit the traversal to only |
34 |
|
%% scan a subset of the table based on term order. |
35 |
|
%% A partially bound key is either a list or a tuple with |
36 |
|
%% a prefix that is fully bound. |
37 |
66 |
R = {{Server, '_', '_', '_'}, '_', '_'}, |
38 |
66 |
Xs = ets:match_object(?TABLE, R), |
39 |
66 |
tuples_to_sessions(Xs). |
40 |
|
|
41 |
|
-spec get_sessions(jid:luser(), jid:lserver()) -> [ejabberd_sm:session()]. |
42 |
|
get_sessions(User, Server) -> |
43 |
39975 |
R = {{Server, User, '_', '_'}, '_', '_'}, |
44 |
39975 |
Xs = ets:match_object(?TABLE, R), |
45 |
39975 |
tuples_to_sessions(Xs). |
46 |
|
|
47 |
|
-spec get_sessions(jid:luser(), jid:lserver(), jid:lresource()) -> |
48 |
|
[ejabberd_sm:session()]. |
49 |
|
get_sessions(User, Server, Resource) -> |
50 |
22014 |
R = {{Server, User, Resource, '_'}, '_', '_'}, |
51 |
22014 |
Xs = ets:match_object(?TABLE, R), |
52 |
|
%% TODO these sessions should be deduplicated. |
53 |
|
%% It is possible, that after merging two cets tables we could end up |
54 |
|
%% with sessions from two nodes for the same full jid. |
55 |
|
%% One of the sessions must be killed. |
56 |
|
%% We can detect duplicates on the merging step or on reading (or both). |
57 |
22014 |
tuples_to_sessions(Xs). |
58 |
|
|
59 |
|
-spec set_session(User :: jid:luser(), |
60 |
|
Server :: jid:lserver(), |
61 |
|
Resource :: jid:lresource(), |
62 |
|
Session :: ejabberd_sm:session()) -> ok | {error, term()}. |
63 |
|
set_session(_User, _Server, _Resource, Session) -> |
64 |
11608 |
cets:insert(?TABLE, session_to_tuple(Session)). |
65 |
|
|
66 |
|
-spec delete_session(SID :: ejabberd_sm:sid(), |
67 |
|
User :: jid:luser(), |
68 |
|
Server :: jid:lserver(), |
69 |
|
Resource :: jid:lresource()) -> ok. |
70 |
|
delete_session(SID, User, Server, Resource) -> |
71 |
5815 |
cets:delete(?TABLE, make_key(User, Server, Resource, SID)). |
72 |
|
|
73 |
|
%% cleanup is called on each node in the cluster, when Node is down |
74 |
|
-spec cleanup(atom()) -> any(). |
75 |
|
cleanup(Node) -> |
76 |
:-( |
KeyPattern = {'_', '_', '_', {'_', '$1'}}, |
77 |
:-( |
Guard = {'==', {node, '$1'}, Node}, |
78 |
:-( |
R = {KeyPattern, '_', '_'}, |
79 |
:-( |
cets:ping_all(?TABLE), |
80 |
|
%% This is a full table scan, but cleanup is rare. |
81 |
:-( |
Tuples = ets:select(?TABLE, [{R, [Guard], ['$_']}]), |
82 |
:-( |
lists:foreach(fun({_Key, _, _} = Tuple) -> |
83 |
:-( |
Session = tuple_to_session(Tuple), |
84 |
:-( |
ejabberd_sm:run_session_cleanup_hook(Session) |
85 |
|
end, Tuples), |
86 |
|
%% We don't need to replicate deletes |
87 |
|
%% We remove the local content here |
88 |
:-( |
ets:select_delete(?TABLE, [{R, [Guard], [true]}]). |
89 |
|
|
90 |
|
-spec total_count() -> integer(). |
91 |
|
total_count() -> |
92 |
106 |
ets:info(?TABLE, size). |
93 |
|
|
94 |
|
%% Counts merged by US |
95 |
|
-spec unique_count() -> integer(). |
96 |
|
unique_count() -> |
97 |
99 |
compute_unique(ets:first(?TABLE), 0). |
98 |
|
|
99 |
|
compute_unique('$end_of_table', Sum) -> |
100 |
98 |
Sum; |
101 |
|
compute_unique({S, U, _, _} = Key, Sum) -> |
102 |
66 |
Key2 = ets:next(?TABLE, Key), |
103 |
66 |
case Key2 of |
104 |
|
{S, U, _, _} -> |
105 |
2 |
compute_unique(Key2, Sum); |
106 |
|
_ -> |
107 |
64 |
compute_unique(Key2, Sum + 1) |
108 |
|
end. |
109 |
|
|
110 |
|
session_to_tuple(#session{sid = SID, usr = {U, S, R}, priority = Prio, info = Info}) -> |
111 |
11608 |
{make_key(U, S, R, SID), Prio, Info}. |
112 |
|
|
113 |
|
make_key(User, Server, Resource, SID) -> |
114 |
17423 |
{Server, User, Resource, SID}. |
115 |
|
|
116 |
|
tuple_to_session({{S, U, R, SID}, Prio, Info}) -> |
117 |
51241 |
#session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = Prio, info = Info}. |
118 |
|
|
119 |
|
tuples_to_sessions(Xs) -> |
120 |
62172 |
[tuple_to_session(X) || X <- Xs]. |