/src/ejabberd_sm_redis.erl

https://github.com/esl/MongooseIM · Erlang · 186 lines · 152 code · 23 blank · 11 comment · 1 complexity · 5aa8a950409474f198e82e20953f9a32 MD5 · raw file

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