./ct_report/coverage/ejabberd_sm_redis.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Konrad Kaplita <konrad.kaplita@erlang-solutions.com>
3 %%% @copyright (C) 2011, Erlang Solutions Ltd.
4 %%% @doc Implementation of Redis-based session manager
5 %%%
6 %%% @end
7 %%% Created : 17 Nov 2011 by Konrad Kaplita <konrad.kaplita@erlang-solutions.com>
8 %%%-------------------------------------------------------------------
9 -module(ejabberd_sm_redis).
10
11 -include("mongoose.hrl").
12 -include("session.hrl").
13
14 -behavior(ejabberd_sm_backend).
15 -export([init/1,
16 get_sessions/0,
17 get_sessions/1,
18 get_sessions/2,
19 get_sessions/3,
20 set_session/4,
21 delete_session/4,
22 cleanup/1,
23 maybe_initial_cleanup/2,
24 total_count/0,
25 unique_count/0]).
26
27 -ignore_xref([maybe_initial_cleanup/2]).
28
29 -spec init(map()) -> any().
30 init(_Opts) ->
31 %% Clean current node's sessions from previous life
32 53 {Elapsed, RetVal} = timer:tc(?MODULE, maybe_initial_cleanup, [node(), true]),
33 53 ?LOG_NOTICE(#{what => sm_cleanup_initial,
34 text => <<"SM cleanup on start took">>,
35 53 duration => erlang:round(Elapsed / 1000)}),
36 53 RetVal.
37
38 -spec get_sessions() -> [ejabberd_sm:session()].
39 get_sessions() ->
40 120 Keys = mongoose_redis:cmd(["KEYS", hash(<<"*">>)]),
41 120 lists:flatmap(fun(K) ->
42 42 Sessions = mongoose_redis:cmd(["SMEMBERS", K]),
43 42 lists:map(fun(S) ->
44 41 binary_to_term(S)
45 end,
46 Sessions)
47 end, Keys).
48
49 -spec get_sessions(jid:server()) -> [ejabberd_sm:session()].
50 get_sessions(Server) ->
51 66 Keys = mongoose_redis:cmd(["KEYS", hash(Server)]),
52 66 lists:flatmap(fun(K) ->
53 51 Sessions = mongoose_redis:cmd(["SMEMBERS", K]),
54 51 lists:map(fun(S) ->
55 51 binary_to_term(S)
56 end,
57 Sessions)
58 end, Keys).
59
60 -spec get_sessions(jid:user(), jid:server()) -> [ejabberd_sm:session()].
61 get_sessions(User, Server) ->
62 45144 Sessions = mongoose_redis:cmd(["SMEMBERS", hash(User, Server)]),
63
64 45144 lists:map(fun(S) -> binary_to_term(S) end, Sessions).
65
66 -spec get_sessions(jid:user(), jid:server(), jid:resource()
67 ) -> [ejabberd_sm:session()].
68 get_sessions(User, Server, Resource) ->
69 47618 Sessions = mongoose_redis:cmd(["SMEMBERS", hash(User, Server, Resource)]),
70
71 47618 lists:map(fun(S) -> binary_to_term(S) end, Sessions).
72
73 -spec set_session(User :: jid:luser(),
74 Server :: jid:lserver(),
75 Resource :: jid:lresource(),
76 Session :: ejabberd_sm:session()) -> ok | {error, term()}.
77 set_session(User, Server, Resource, Session) ->
78 13454 OldSessions = get_sessions(User, Server, Resource),
79 13454 Node = sid_to_node(Session#session.sid),
80 13454 case lists:keysearch(Session#session.sid, #session.sid, OldSessions) of
81 {value, OldSession} ->
82 6444 BOldSession = term_to_binary(OldSession),
83 6444 BSession = term_to_binary(Session),
84 6444 mongoose_redis:cmds([["SADD", n(Node), hash(User, Server, Resource, Session#session.sid)],
85 ["SREM", hash(User, Server), BOldSession],
86 ["SREM", hash(User, Server, Resource), BOldSession],
87 ["SADD", hash(User, Server), BSession],
88 ["SADD", hash(User, Server, Resource), BSession]]);
89 false ->
90 7010 BSession = term_to_binary(Session),
91 7010 mongoose_redis:cmds([["SADD", n(Node), hash(User, Server, Resource, Session#session.sid)],
92 ["SADD", hash(User, Server), BSession],
93 ["SADD", hash(User, Server, Resource), BSession]])
94 end.
95
96 -spec delete_session(SID :: ejabberd_sm:sid(),
97 User :: jid:user(),
98 Server :: jid:server(),
99 Resource :: jid:resource()) -> ok.
100 delete_session(SID, User, Server, Resource) ->
101 7012 Sessions = get_sessions(User, Server, Resource),
102 7012 case lists:keysearch(SID, #session.sid, Sessions) of
103 {value, Session} ->
104 7010 BSession = term_to_binary(Session),
105 7010 mongoose_redis:cmds([["SREM", hash(User, Server), BSession],
106 ["SREM", hash(User, Server, Resource), BSession],
107 ["SREM", n(sid_to_node(SID)), hash(User, Server, Resource, SID)]]);
108 false ->
109 2 ok
110 end.
111
112 -spec cleanup(atom()) -> any().
113 cleanup(Node) ->
114 1 maybe_initial_cleanup(Node, false).
115
116 -spec maybe_initial_cleanup(atom(), boolean()) -> any().
117 maybe_initial_cleanup(Node, Initial) ->
118 54 Hashes = mongoose_redis:cmd(["SMEMBERS", n(Node)]),
119 54 mongoose_redis:cmd(["DEL", n(Node)]),
120 54 Sessions = lists:map(fun(H) ->
121 349 [_, U, S, R | SIDEncoded] = re:split(H, ":"),
122 %% Add possible removed ":" from encoded SID
123 349 SID = binary_to_term(mongoose_bin:join(SIDEncoded, <<":">>)),
124 349 delete_session(SID, U, S, R),
125 349 case Initial of
126 true ->
127 4 ok;
128 false ->
129 345 Session = #session{usr = {U, S, R}, sid = SID},
130 345 ejabberd_sm:session_cleanup(Session),
131 345 Session
132 end
133 end, Hashes),
134 54 ejabberd_sm:sessions_cleanup(Sessions).
135
136 -spec total_count() -> integer().
137 total_count() ->
138 126 {Counts, _} = rpc:multicall(ejabberd_sm, get_node_sessions_number, []),
139 126 lists:sum([Count || Count <- Counts, is_integer(Count)]).
140
141 -spec unique_count() -> integer().
142 unique_count() ->
143 119 length(mongoose_redis:cmd(["KEYS", "s2:*"])).
144
145 %% Internal functions
146
147 -spec hash(binary()) -> iolist().
148 hash(Val1) ->
149 186 ["s3:*:", Val1, ":*"].
150
151 -spec hash(binary(), binary()) -> iolist().
152 hash(Val1, Val2) ->
153 72052 ["s2:", Val1, ":", Val2].
154
155 -spec hash(binary(), binary(), binary()) -> iolist().
156 hash(Val1, Val2, Val3) ->
157 74526 ["s3:", Val1, ":", Val2, ":", Val3].
158
159 -spec hash(binary(), binary(), binary(), ejabberd_sm:sid()) -> iolist().
160 hash(Val1, Val2, Val3, Val4) ->
161 20464 ["s4:", Val1, ":", Val2, ":", Val3, ":", term_to_binary(Val4)].
162
163 -spec n(atom()) -> iolist().
164 n(Node) ->
165 20572 ["n:", atom_to_list(Node)].
166
167 sid_to_node(SID) ->
168 20464 {_, Pid} = SID,
169 20464 node(Pid).
Line Hits Source