25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-22 16:20:52 +01:00

mod_caps: reduce memory and remove mnesia lock

SVN Revision: 2050
This commit is contained in:
Christophe Romain 2009-04-30 21:09:45 +00:00
parent 5ae96e4065
commit 0ed0c45aba
2 changed files with 63 additions and 64 deletions

View File

@ -17,6 +17,9 @@
* src/mod_pubsub/gen_pubsub_node.erl: Likewise * src/mod_pubsub/gen_pubsub_node.erl: Likewise
* src/mod_pubsub/gen_pubsub_nodetree.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> 2009-04-28 Badlop <badlop@process-one.net>
* src/ejabberd_hooks.erl: Support distributed hooks (EJAB-829) * src/ejabberd_hooks.erl: Support distributed hooks (EJAB-829)

View File

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