diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 309287721..0a3cfe6b5 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -286,7 +286,8 @@ init([StateName, StateData, _FSMLimitOpts]) -> NewStateData = StateData#state{sid = SID, socket_monitor = MRef}, {ok, StateName, NewStateData}; true -> - {ok, StateName, StateData#state{socket_monitor = MRef}} + %% pack, as sharing is not preserved accross message passing + {ok, StateName, pack(StateData#state{socket_monitor = MRef})} end. %% Return list of all available resources of contacts, @@ -2148,11 +2149,22 @@ maybe_migrate(StateName, StateData) -> After = ejabberd_cluster:rehash_timeout(), migrate(self(), NewNode, After) end, - fsm_next_state(StateName, StateData); + fsm_next_state_pack(StateName, StateData); Node -> fsm_migrate(StateName, StateData, Node, 0) end. +%% fsm_next_state_pack: Pack the StateData structure to improve +%% sharing. +fsm_next_state_pack(StateName, StateData) -> + fsm_next_state_gc(StateName, pack(StateData)). + +%% fsm_next_state_gc: Garbage collect the process heap to make use of +%% the newly packed StateData structure. +fsm_next_state_gc(StateName, PackedStateData) -> + erlang:garbage_collect(), + fsm_next_state(StateName, PackedStateData). + %% fsm_next_state: Generate the next_state FSM tuple with different %% timeout, depending on the future state fsm_next_state(session_established, StateData) -> @@ -2231,3 +2243,54 @@ bounce_messages() -> after 0 -> ok end. + + + +%%%---------------------------------------------------------------------- +%%% JID Set memory footprint reduction code +%%%---------------------------------------------------------------------- + +%% Try to reduce the heap footprint of the four presence sets +%% by ensuring that we re-use strings and Jids wherever possible. +pack(S = #state{pres_a=A, + pres_i=I, + pres_f=F, + pres_t=T}) -> + {NewA, Pack1} = pack_jid_set(A, gb_trees:empty()), + {NewI, Pack2} = pack_jid_set(I, Pack1), + {NewF, Pack3} = pack_jid_set(F, Pack2), + {NewT, _Pack4} = pack_jid_set(T, Pack3), + %% Throw away Pack4 so that if we delete references to + %% Strings or Jids in any of the sets there will be + %% no live references for the GC to find. + S#state{pres_a=NewA, + pres_i=NewI, + pres_f=NewF, + pres_t=NewT}. + +pack_jid_set(Set, Pack) -> + Jids = ?SETS:to_list(Set), + {PackedJids, NewPack} = pack_jids(Jids, Pack, []), + {?SETS:from_list(PackedJids), NewPack}. + +pack_jids([], Pack, Acc) -> {Acc, Pack}; +pack_jids([{U,S,R}=Jid | Jids], Pack, Acc) -> + case gb_trees:lookup(Jid, Pack) of + {value, PackedJid} -> + pack_jids(Jids, Pack, [PackedJid | Acc]); + none -> + {NewU, Pack1} = pack_string(U, Pack), + {NewS, Pack2} = pack_string(S, Pack1), + {NewR, Pack3} = pack_string(R, Pack2), + NewJid = {NewU, NewS, NewR}, + NewPack = gb_trees:insert(NewJid, NewJid, Pack3), + pack_jids(Jids, NewPack, [NewJid | Acc]) + end. + +pack_string(String, Pack) -> + case gb_trees:lookup(String, Pack) of + {value, PackedString} -> + {PackedString, Pack}; + none -> + {String, gb_trees:insert(String, String, Pack)} + end.