]> granicus.if.org Git - ejabberd/commitdiff
mod_caps: reduce memory and remove mnesia lock
authorChristophe Romain <christophe.romain@process-one.net>
Thu, 30 Apr 2009 21:09:45 +0000 (21:09 +0000)
committerChristophe Romain <christophe.romain@process-one.net>
Thu, 30 Apr 2009 21:09:45 +0000 (21:09 +0000)
SVN Revision: 2050

ChangeLog
src/mod_caps.erl

index 1a2b52f8fc46264a8ecb3be8a4f61bd592d54aca..16e43fbb6962bdeb1dbd0a4885868e907fb86e0f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -17,6 +17,9 @@
        * src/mod_pubsub/gen_pubsub_node.erl: Likewise
        * src/mod_pubsub/gen_pubsub_nodetree.erl: Likewise
 
+       * src/mod_caps.erl: Reduce memory consumption and remove mnesia table
+       locking, improving performances
+
 2009-04-28  Badlop  <badlop@process-one.net>
 
        * src/ejabberd_hooks.erl: Support distributed hooks (EJAB-829)
index dca7a3c312d662dc64855aec39e10049020b4d62..6707db0354e6cd7989d26c00ef307dd0a6f0e61a 100644 (file)
@@ -22,6 +22,8 @@
 %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 %%% 02111-1307 USA
 %%%
+%%% 2009, improvements from Process-One to support correct PEP handling
+%%% through s2s, use less memory, and speedup global caps handling
 %%%----------------------------------------------------------------------
 
 -module(mod_caps).
@@ -63,7 +65,7 @@
 -define(CAPS_QUERY_TIMEOUT, 60000). % 1mn without answer, consider client never answer
 
 -record(caps, {node, version, exts}).
--record(caps_features, {node_pair, features}).
+-record(caps_features, {node_pair, features = []}).
 -record(user_caps, {jid, caps}).
 -record(user_caps_resources, {uid, resource}).
 -record(state, {host,
@@ -100,7 +102,7 @@ read_caps([], Result) ->
 
 %% get_caps reads user caps from database
 get_caps(JID) ->
-    case catch mnesia:dirty_read({user_caps, list_to_binary(jlib:jid_to_string(JID))}) of
+    case catch mnesia:dirty_read({user_caps, jid_to_binary(JID)}) of
        [#user_caps{caps=Caps}] -> 
            Caps;
        _ -> 
@@ -110,16 +112,15 @@ get_caps(JID) ->
 %% clear_caps removes user caps from database
 clear_caps(JID) ->
     {U, S, R} = jlib:jid_tolower(JID),
-    BJID = list_to_binary(jlib:jid_to_string(JID)),
-    BUID = list_to_binary(jlib:jid_to_string({U, S, []})),
+    BJID = jid_to_binary(JID),
+    BUID = jid_to_binary({U, S, []}),
     catch mnesia:dirty_delete({user_caps, BJID}),
     catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}),
     ok.
 
 %% give default user resource
 get_user_resources(LUser, LServer) ->
-    BUID = list_to_binary(jlib:jid_to_string({LUser, LServer, []})),
-    case catch mnesia:dirty_read({user_caps_resources, BUID}) of
+    case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of
        {'EXIT', _} ->
            [];
        Resources ->
@@ -196,19 +197,32 @@ receive_packet(_, _, _) ->
 receive_packet(_JID, From, To, Packet) ->
     receive_packet(From, To, Packet).
 
+jid_to_binary(JID) ->
+    list_to_binary(jlib:jid_to_string(JID)).
+
+caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) ->
+    BExts = [list_to_binary(Ext) || Ext <- Exts],
+    #caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}.
+
+node_to_binary(Node, SubNode) ->
+    {list_to_binary(Node), list_to_binary(SubNode)}.
+
+features_to_binary(L) -> [list_to_binary(I) || I <- L].
+binary_to_features(L) -> [binary_to_list(I) || I <- L].
+
 %%====================================================================
 %% gen_server callbacks
 %%====================================================================
 
 init([Host, _Opts]) ->
     mnesia:create_table(caps_features,
-                       [{ram_copies, [node()]},
+                       [{disc_copies, [node()]},
                         {attributes, record_info(fields, caps_features)}]),
     mnesia:create_table(user_caps,
-                       [{disc_copies, [node()]},
+                       [{ram_copies, [node()]},
                         {attributes, record_info(fields, user_caps)}]),
     mnesia:create_table(user_caps_resources,
-                       [{disc_copies, [node()]},
+                       [{ram_copies, [node()]},
                         {type, bag},
                         {attributes, record_info(fields, user_caps_resources)}]),
     mnesia:delete_table(user_caps_default),
@@ -220,26 +234,21 @@ init([Host, _Opts]) ->
 
 maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
     SubNodes = [Version | Exts],
-    F = fun() ->
-               %% Make sure that we have all nodes we need to know.
-               %% If a single one is missing, we wait for more disco
-               %% responses.
-               lists:foldl(fun(SubNode, Acc) ->
-                                   case Acc of
-                                       fail -> fail;
-                                       _ ->
-                                           case mnesia:read({caps_features, {Node, SubNode}}) of
-                                               [] -> fail;
-                                               [#caps_features{features = Features}] -> Features ++ Acc
-                                           end
-                                   end
-                           end, [], SubNodes)
-       end,
-    case mnesia:transaction(F) of
-       {atomic, fail} ->
-           wait;
-       {atomic, Features} ->
-           {ok, Features}
+    %% Make sure that we have all nodes we need to know.
+    %% If a single one is missing, we wait for more disco
+    %% responses.
+    case lists:foldl(fun(SubNode, Acc) ->
+                       case Acc of
+                           fail -> fail;
+                           _ ->
+                               case mnesia:dirty_read({caps_features, {Node, SubNode}}) of
+                                   [] -> fail;
+                                   [#caps_features{features = Features}] -> Features ++ Acc %% TODO binary
+                               end
+                       end
+               end, [], SubNodes) of
+       fail -> wait;
+       Features -> {ok, Features}
     end.
 
 timestamp() ->
@@ -249,7 +258,7 @@ timestamp() ->
 handle_call({get_features, Caps}, From, State) ->
     case maybe_get_features(Caps) of
        {ok, Features} -> 
-           {reply, Features, State};
+           {reply, binary_to_features(Features), State};
        wait ->
            gen_server:cast(self(), visit_feature_queries),
            Timeout = timestamp() + 10,
@@ -268,30 +277,29 @@ handle_cast({note_caps, From,
     %% XXX: this leads to race conditions where ejabberd will send
     %% lots of caps disco requests.
     {U, S, R} = jlib:jid_tolower(From),
-    BJID = list_to_binary(jlib:jid_to_string(From)),
-    mnesia:dirty_write(#user_caps{jid = BJID, caps = Caps}),
+    BJID = jid_to_binary(From),
+    mnesia:dirty_write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
     case ejabberd_sm:get_user_resources(U, S) of
        [] ->
            % only store resources of caps aware external contacts
-           BUID = list_to_binary(jlib:jid_to_string(jlib:jid_remove_resource(From))),
+           BUID = jid_to_binary({U, S, []}),
            mnesia:dirty_write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
        _ ->
            ok
     end,
-    SubNodes = [Version | Exts],
     %% Now, find which of these are not already in the database.
-    Fun = fun() ->
-                 lists:foldl(fun(SubNode, Acc) ->
-                                     case mnesia:read({caps_features, {Node, SubNode}}) of
-                                         [] ->
-                                             [SubNode | Acc];
-                                         _ ->
-                                             Acc
-                                     end
-                             end, [], SubNodes)
-         end,
-    case mnesia:transaction(Fun) of
-       {atomic, Missing} ->
+    SubNodes = [Version | Exts],
+    case lists:foldl(fun(SubNode, Acc) ->
+                               case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of
+                                   [] ->
+                                       [SubNode | Acc];
+                                   _ ->
+                                       Acc
+                               end
+                       end, [], SubNodes) of
+       [] ->
+           {noreply, State};
+       Missing ->
            %% For each unknown caps "subnode", we send a disco request.
            NewRequests = lists:foldl(
                fun(SubNode, Dict) ->
@@ -308,12 +316,9 @@ handle_cast({note_caps, From,
                            (Host, ID, ?MODULE, handle_disco_response),
                          ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
                          timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID}),
-                         ?DICT:store(ID, {Node, SubNode}, Dict)
+                         ?DICT:store(ID, node_to_binary(Node, SubNode), Dict)
                  end, Requests, Missing),
-           {noreply, State#state{disco_requests = NewRequests}};
-       Error ->
-           ?ERROR_MSG("Transaction failed: ~p", [Error]),
-           {noreply, State}
+           {noreply, State#state{disco_requests = NewRequests}}
     end;
 handle_cast({disco_response, From, _To, 
             #iq{type = Type, id = ID,
@@ -322,18 +327,14 @@ handle_cast({disco_response, From, _To,
     case {Type, SubEls} of
        {result, [{xmlelement, "query", _Attrs, Els}]} ->
            case ?DICT:find(ID, Requests) of
-               {ok, {Node, SubNode}} ->
+               {ok, BinaryNode} ->
                    Features =
                        lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
                                              [xml:get_attr_s("var", FAttrs)];
                                         (_) ->
                                              []
                                      end, Els),
-                   mnesia:transaction(
-                     fun() ->
-                             mnesia:write(#caps_features{node_pair = {Node, SubNode},
-                                                         features = Features})
-                     end),
+                   mnesia:dirty_write(#caps_features{node_pair = BinaryNode, features = features_to_binary(Features)}),
                    gen_server:cast(self(), visit_feature_queries);
                error ->
                    ?ERROR_MSG("ID '~s' matches no query", [ID])
@@ -341,13 +342,8 @@ handle_cast({disco_response, From, _To,
        {error, _} ->
            %% XXX: if we get error, we cache empty feature not to probe the client continuously
            case ?DICT:find(ID, Requests) of
-               {ok, {Node, SubNode}} ->
-                   Features = [],
-                   mnesia:transaction(
-                     fun() ->
-                             mnesia:write(#caps_features{node_pair = {Node, SubNode},
-                                                         features = Features})
-                     end),
+               {ok, BinaryNode} ->
+                   mnesia:write(#caps_features{node_pair = BinaryNode}),
                    gen_server:cast(self(), visit_feature_queries);
                error ->
                    ?ERROR_MSG("ID '~s' matches no query", [ID])