Reduce memory consumption due to caps handling

SVN Revision: 1712
This commit is contained in:
Christophe Romain 2008-12-08 14:10:55 +00:00
parent 4618ec1fef
commit e2d8154a80
4 changed files with 102 additions and 118 deletions

View File

@ -1,3 +1,9 @@
2008-12-08 Christophe Romain <christophe.romain@process-one.net>
* src/ejabberd_c2s.erl: Reduce memory consumption due to caps handling
* src/mod_pubsub/mod_pubsub.erl: Likewise
* src/mod_caps.erl: Likewise
2008-12-08 Mickael Remond <mremond@process-one.net>
* src/ejabberd_c2s.erl: Enforce client stanza from attribute

View File

@ -37,8 +37,7 @@
send_element/2,
socket_type/0,
get_presence/1,
get_subscribed/1,
get_subscribed_and_online/1]).
get_subscribed/1]).
%% gen_fsm callbacks
-export([init/1,
@ -84,7 +83,6 @@
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_available = ?DICT:new(),
pres_last, pres_pri,
pres_timestamp,
pres_invis = false,
@ -214,14 +212,8 @@ init([{SockMod, Socket}, Opts]) ->
end.
%% Return list of all available resources of contacts,
%% in form [{JID, Caps}].
get_subscribed(FsmRef) ->
gen_fsm:sync_send_all_state_event(
FsmRef, get_subscribed, 1000).
get_subscribed_and_online(FsmRef) ->
gen_fsm:sync_send_all_state_event(
FsmRef, get_subscribed_and_online, 1000).
gen_fsm:sync_send_all_state_event(FsmRef, get_subscribed, 1000).
%%----------------------------------------------------------------------
%% Func: StateName/2
@ -1032,29 +1024,8 @@ handle_sync_event({get_presence}, _From, StateName, StateData) ->
fsm_reply(Reply, StateName, StateData);
handle_sync_event(get_subscribed, _From, StateName, StateData) ->
Subscribed = StateData#state.pres_f,
Online = StateData#state.pres_available,
Pred = fun(User, _Caps) ->
?SETS:is_element(jlib:jid_remove_resource(User),
Subscribed) orelse
?SETS:is_element(User, Subscribed)
end,
SubscribedAndOnline = ?DICT:filter(Pred, Online),
SubscribedWithCaps = ?SETS:fold(fun(User, Acc) ->
[{User, undefined}|Acc]
end, ?DICT:to_list(SubscribedAndOnline), Subscribed),
{reply, SubscribedWithCaps, StateName, StateData};
handle_sync_event(get_subscribed_and_online, _From, StateName, StateData) ->
Subscribed = StateData#state.pres_f,
Online = StateData#state.pres_available,
Pred = fun(User, _Caps) ->
?SETS:is_element(jlib:jid_remove_resource(User),
Subscribed) orelse
?SETS:is_element(User, Subscribed)
end,
SubscribedAndOnline = ?DICT:filter(Pred, Online),
{reply, ?DICT:to_list(SubscribedAndOnline), StateName, StateData};
Subscribed = ?SETS:to_list(StateData#state.pres_f),
{reply, Subscribed, StateName, StateData};
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
@ -1147,41 +1118,39 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
LFrom = jlib:jid_tolower(From),
LBFrom = jlib:jid_remove_resource(LFrom),
%% Note contact availability
Caps = mod_caps:read_caps(Els),
mod_caps:note_caps(StateData#state.server, From, Caps),
NewAvailable = case xml:get_attr_s("type", Attrs) of
"unavailable" ->
?DICT:erase(LFrom, StateData#state.pres_available);
_ ->
?DICT:store(LFrom, Caps, StateData#state.pres_available)
end,
NewStateData = StateData#state{pres_available = NewAvailable},
case xml:get_attr_s("type", Attrs) of
"unavailable" ->
mod_caps:clear_caps(From);
_ ->
Caps = mod_caps:read_caps(Els),
mod_caps:note_caps(StateData#state.server, From, Caps)
end,
case ?SETS:is_element(
LFrom, NewStateData#state.pres_a) orelse
LFrom, StateData#state.pres_a) orelse
?SETS:is_element(
LBFrom, NewStateData#state.pres_a) of
LBFrom, StateData#state.pres_a) of
true ->
{true, Attrs, NewStateData};
{true, Attrs, StateData};
false ->
case ?SETS:is_element(
LFrom, NewStateData#state.pres_f) of
LFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LFrom,
NewStateData#state.pres_a),
StateData#state.pres_a),
{true, Attrs,
NewStateData#state{pres_a = A}};
StateData#state{pres_a = A}};
false ->
case ?SETS:is_element(
LBFrom, NewStateData#state.pres_f) of
LBFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LBFrom,
NewStateData#state.pres_a),
StateData#state.pres_a),
{true, Attrs,
NewStateData#state{pres_a = A}};
StateData#state{pres_a = A}};
false ->
{true, Attrs, NewStateData}
{true, Attrs, StateData}
end
end
end;

View File

@ -31,7 +31,9 @@
-behaviour(gen_mod).
-export([read_caps/1,
get_caps/1,
note_caps/3,
clear_caps/1,
get_features/2,
handle_disco_response/3]).
@ -57,6 +59,7 @@
-record(caps, {node, version, exts}).
-record(caps_features, {node_pair, features}).
-record(user_caps, {jid, caps}).
-record(state, {host,
disco_requests = ?DICT:new(),
feature_queries = []}).
@ -89,12 +92,26 @@ read_caps([_ | Tail], Result) ->
read_caps([], Result) ->
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
[#user_caps{caps=Caps}] ->
Caps;
_ ->
nothing
end.
%% clear_caps removes user caps from database
clear_caps(JID) ->
catch mnesia:dirty_delete({user_caps, list_to_binary(jlib:jid_to_string(JID))}).
%% note_caps should be called to make the module request disco
%% information. Host is the host that asks, From is the full JID that
%% sent the caps packet, and Caps is what read_caps returned.
note_caps(Host, From, Caps) ->
case Caps of
nothing -> ok;
nothing ->
ok;
_ ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {note_caps, From, Caps})
@ -138,7 +155,9 @@ init([Host, _Opts]) ->
mnesia:create_table(caps_features,
[{ram_copies, [node()]},
{attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), ram_copies),
mnesia:create_table(user_caps,
[{disc_copies, [node()]},
{attributes, record_info(fields, user_caps)}]),
{ok, #state{host = Host}}.
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
@ -186,10 +205,12 @@ handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
handle_cast({note_caps, From,
#caps{node = Node, version = Version, exts = Exts}},
#caps{node = Node, version = Version, exts = Exts} = Caps},
#state{host = Host, disco_requests = Requests} = State) ->
%% XXX: this leads to race conditions where ejabberd will send
%% lots of caps disco requests.
mnesia:dirty_write(#user_caps{jid = list_to_binary(jlib:jid_to_string(From)),
caps = Caps}),
SubNodes = [Version | Exts],
%% Now, find which of these are not already in the database.
Fun = fun() ->
@ -204,11 +225,9 @@ handle_cast({note_caps, From,
end,
case mnesia:transaction(Fun) of
{atomic, Missing} ->
%% For each unknown caps "subnode", we send a disco
%% request.
NewRequests =
lists:foldl(
fun(SubNode, Dict) ->
%% For each unknown caps "subnode", we send a disco request.
NewRequests = lists:foldl(
fun(SubNode, Dict) ->
ID = randoms:get_string(),
Stanza =
{xmlelement, "iq",

View File

@ -478,17 +478,14 @@ handle_cast({presence, JID, Pid}, State) ->
end, State#state.plugins),
%% and send to From last PEP events published by its contacts
case catch ejabberd_c2s:get_subscribed(Pid) of
ContactsWithCaps when is_list(ContactsWithCaps) ->
Caps = proplists:get_value(LJID, ContactsWithCaps),
ContactsUsers = lists:usort(lists:map(
fun({{User, Server, _}, _}) -> {User, Server} end, ContactsWithCaps)),
Contacts when is_list(Contacts) ->
lists:foreach(
fun({User, Server}) ->
PepKey = {User, Server, ""},
fun({User, Server, _}) ->
Owner = {User, Server, ""},
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, options = Options}) ->
case get_option(Options, send_last_published_item) of
on_sub_and_presence ->
case is_caps_notify(ServerHost, Node, Caps) of
case is_caps_notify(ServerHost, Node, LJID) of
true ->
Subscribed = case get_option(Options, access_model) of
open -> true;
@ -500,8 +497,7 @@ handle_cast({presence, JID, Pid}, State) ->
element(2, get_roster_info(User, Server, LJID, Grps))
end,
if Subscribed ->
?DEBUG("send ~s's ~s event to ~s",[jlib:jid_to_string(PepKey),Node,jlib:jid_to_string(LJID)]),
send_last_item(PepKey, Node, LJID);
send_last_item(Owner, Node, LJID);
true ->
ok
end;
@ -511,8 +507,8 @@ handle_cast({presence, JID, Pid}, State) ->
_ ->
ok
end
end, tree_action(Host, get_nodes, [PepKey]))
end, ContactsUsers);
end, tree_action(Host, get_nodes, [Owner]))
end, Contacts);
_ ->
ok
end,
@ -2330,58 +2326,52 @@ broadcast_config_notification(Host, Node, Lang) ->
%% broadcast Stanza to all contacts of the user that are advertising
%% interest in this kind of Node.
broadcast_by_caps({LUser, LServer, LResource}, Node, _Type, Stanza) ->
?DEBUG("looking for pid of ~p@~p/~p", [LUser, LServer, LResource]),
%% We need to know the resource, so we can ask for presence data.
SenderResource = case LResource of
"" ->
%% If we don't know the resource, just pick one.
case ejabberd_sm:get_user_resources(LUser, LServer) of
[R|_] ->
R;
[] ->
""
end;
_ ->
LResource
end,
case SenderResource of
"" ->
?DEBUG("~p@~p is offline; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
ok;
_ ->
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
C2SPid when is_pid(C2SPid) ->
%% set the from address on the notification to the bare JID of the account owner
%% Also, add "replyto" if entity has presence subscription to the account owner
%% See XEP-0163 1.1 section 4.3.1
Sender = jlib:make_jid(LUser, LServer, ""),
%%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used
case catch ejabberd_c2s:get_subscribed_and_online(C2SPid) of
ContactsWithCaps when is_list(ContactsWithCaps) ->
?DEBUG("found contacts with caps: ~p", [ContactsWithCaps]),
lists:foreach(
fun({JID, Caps}) ->
case is_caps_notify(LServer, Node, Caps) of
true ->
To = jlib:make_jid(JID),
ejabberd_router ! {route, Sender, To, Stanza};
false ->
ok
SenderResource = user_resource(LUser, LServer, LResource),
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
C2SPid when is_pid(C2SPid) ->
%% set the from address on the notification to the bare JID of the account owner
%% Also, add "replyto" if entity has presence subscription to the account owner
%% See XEP-0163 1.1 section 4.3.1
Sender = jlib:make_jid(LUser, LServer, ""),
%%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used
case catch ejabberd_c2s:get_subscribed(C2SPid) of
Contacts when is_list(Contacts) ->
Online = lists:foldl(fun({U, S, R}, Acc) ->
case user_resource(U, S, R) of
[] -> Acc;
OR -> [{U, S, OR}|Acc]
end
end, ContactsWithCaps);
_ ->
ok
end,
ok;
_ ->
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
ok
end
end, [], Contacts),
lists:foreach(fun(LJID) ->
case is_caps_notify(LServer, Node, LJID) of
true ->
ejabberd_router ! {route, Sender, jlib:make_jid(LJID), Stanza};
false ->
ok
end
end, Online);
_ ->
ok
end,
ok;
_ ->
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
ok
end;
broadcast_by_caps(_, _, _, _) ->
ok.
is_caps_notify(Host, Node, Caps) ->
user_resource(LUser, LServer, []) ->
%% If we don't know the resource, just pick first if any
case ejabberd_sm:get_user_resources(LUser, LServer) of
[R|_] -> R;
[] -> []
end;
user_resource(_, _, LResource) ->
LResource.
is_caps_notify(Host, Node, LJID) ->
Caps = mod_caps:get_caps(LJID),
case catch mod_caps:get_features(Host, Caps) of
Features when is_list(Features) -> lists:member(Node ++ "+notify", Features);
_ -> false