diff --git a/ChangeLog b/ChangeLog index 1a2b52f8f..16e43fbb6 100644 --- 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 * src/ejabberd_hooks.erl: Support distributed hooks (EJAB-829) diff --git a/src/mod_caps.erl b/src/mod_caps.erl index dca7a3c31..6707db035 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -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])