From 8d3d59535ddbd85c552061080debd32381472283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20R=C3=A9mond?= Date: Fri, 14 Sep 2007 14:18:01 +0000 Subject: [PATCH] * src/ejabberd_c2s.erl: Hibernate inactive c2s processes (EJAB-356). SVN Revision: 934 --- src/ejabberd_c2s.erl | 178 +++++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 81 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 6b933ef6d..32e186f3c 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -87,6 +87,7 @@ %% This is the timeout to apply between event when starting a new %% session: -define(C2S_OPEN_TIMEOUT, 5000). +-define(C2S_HIBERNATE_TIMEOUT, 90000). -define(STREAM_HEADER, "" @@ -266,11 +267,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> c2s_stream_features, Server, [], [])}), - {next_state, wait_for_feature_request, - StateData#state{server = Server, - sasl_state = SASLState, - lang = Lang}, - ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_feature_request, + StateData#state{ + server = Server, + sasl_state = SASLState, + lang = Lang}); _ -> case StateData#state.resource of "" -> @@ -281,17 +282,18 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> [{"xmlns", ?NS_BIND}], []}, {xmlelement, "session", [{"xmlns", ?NS_SESSION}], []}]}), - {next_state, wait_for_bind, - StateData#state{server = Server, - lang = Lang}, - ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_bind, + StateData#state{ + server = Server, + lang = Lang}); _ -> send_element( StateData, {xmlelement, "stream:features", [], []}), - {next_state, wait_for_session, - StateData#state{server = Server, - lang = Lang}} + fsm_next_state(wait_for_session, + StateData#state{ + server = Server, + lang = Lang}) end end; _ -> @@ -311,9 +313,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> {stop, normal, StateData}; true -> send_text(StateData, Header), - {next_state, wait_for_auth, - StateData#state{server = Server, - lang = Lang}} + fsm_next_state(wait_for_auth, + StateData#state{ + server = Server, + lang = Lang}) end end; _ -> @@ -385,13 +388,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> ]}]} end, send_element(StateData, Res), - {next_state, wait_for_auth, StateData, ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_auth, StateData); {auth, _ID, set, {_U, _P, _D, ""}} -> Err = jlib:make_error_reply( El, ?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)), send_element(StateData, Err), - {next_state, wait_for_auth, StateData, ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_auth, StateData); {auth, _ID, set, {U, P, D, R}} -> JID = jlib:make_jid(U, StateData#state.server, R), case (JID /= error) andalso @@ -428,14 +431,15 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> privacy_get_user_list, StateData#state.server, none, [U, StateData#state.server]), - {next_state, session_established, - StateData#state{user = U, + fsm_next_state(session_established, + StateData#state{ + user = U, resource = R, jid = JID, sid = SID, pres_f = ?SETS:from_list(Fs1), pres_t = ?SETS:from_list(Ts1), - privacy_list = PrivList}}; + privacy_list = PrivList}); _ -> ?INFO_MSG( "(~w) Failed legacy authentication for ~s", @@ -444,8 +448,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> Err = jlib:make_error_reply( El, ?ERR_NOT_AUTHORIZED), send_element(StateData, Err), - {next_state, wait_for_auth, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_auth, StateData) end; _ -> if @@ -456,8 +459,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> [StateData#state.socket, U, R]), Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED), send_element(StateData, Err), - {next_state, wait_for_auth, StateData, - ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_auth, StateData); true -> ?INFO_MSG( "(~w) Forbidden legacy authentication for ~s", @@ -465,13 +467,12 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> jlib:jid_to_string(JID)]), Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), send_element(StateData, Err), - {next_state, wait_for_auth, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_auth, StateData) end end; _ -> process_unauthenticated_stanza(StateData, El), - {next_state, wait_for_auth, StateData, ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_auth, StateData) end; wait_for_auth(timeout, StateData) -> @@ -512,20 +513,20 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> U = xml:get_attr_s(username, Props), ?INFO_MSG("(~w) Accepted authentication for ~s", [StateData#state.socket, U]), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), + fsm_next_state(wait_for_stream, + StateData#state{ + streamid = new_id(), authenticated = true, - user = U - }, ?C2S_OPEN_TIMEOUT}; + user = U }); {continue, ServerOut, NewSASLState} -> send_element(StateData, {xmlelement, "challenge", [{"xmlns", ?NS_SASL}], [{xmlcdata, jlib:encode_base64(ServerOut)}]}), - {next_state, wait_for_sasl_response, - StateData#state{sasl_state = NewSASLState}, - ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_sasl_response, + StateData#state{ + sasl_state = NewSASLState}); {error, Error, Username} -> ?INFO_MSG( "(~w) Failed authentication for ~s@~s", @@ -542,8 +543,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> {xmlelement, "failure", [{"xmlns", ?NS_SASL}], [{xmlelement, Error, [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_feature_request, StateData) end; {?NS_TLS, "starttls"} when TLS == true, TLSEnabled == false, @@ -562,11 +562,11 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> Socket, TLSOpts, xml:element_to_string( {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), - {next_state, wait_for_stream, - StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true - }, ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_stream, + StateData#state{socket = TLSSocket, + streamid = new_id(), + tls_enabled = true + }); {?NS_COMPRESS, "compress"} when Zlib == true, SockMod == gen_tcp -> case xml:get_subtag(El, "method") of @@ -575,8 +575,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> {xmlelement, "failure", [{"xmlns", ?NS_COMPRESS}], [{xmlelement, "setup-failed", [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_feature_request, StateData); Method -> case xml:get_tag_cdata(Method) of "zlib" -> @@ -586,18 +585,18 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> xml:element_to_string( {xmlelement, "compressed", [{"xmlns", ?NS_COMPRESS}], []})), - {next_state, wait_for_stream, + fsm_next_state(wait_for_stream, StateData#state{socket = ZlibSocket, streamid = new_id() - }, ?C2S_OPEN_TIMEOUT}; + }); _ -> send_element(StateData, {xmlelement, "failure", [{"xmlns", ?NS_COMPRESS}], [{xmlelement, "unsupported-method", [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_feature_request, + StateData) end end; _ -> @@ -611,8 +610,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> {stop, normal, StateData}; true -> process_unauthenticated_stanza(StateData, El), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_feature_request, StateData) end end; @@ -647,19 +645,19 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) -> U = xml:get_attr_s(username, Props), ?INFO_MSG("(~w) Accepted authentication for ~s", [StateData#state.socket, U]), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), + fsm_next_state(wait_for_stream, + StateData#state{ + streamid = new_id(), authenticated = true, - user = U - }, ?C2S_OPEN_TIMEOUT}; + user = U}); {continue, ServerOut, NewSASLState} -> send_element(StateData, {xmlelement, "challenge", [{"xmlns", ?NS_SASL}], [{xmlcdata, jlib:encode_base64(ServerOut)}]}), - {next_state, wait_for_sasl_response, - StateData#state{sasl_state = NewSASLState}}; + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); {error, Error, Username} -> ?INFO_MSG( "(~w) Failed authentication for ~s@~s", @@ -669,20 +667,17 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) -> {xmlelement, "failure", [{"xmlns", ?NS_SASL}], [{xmlelement, Error, [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_feature_request, StateData); {error, Error} -> send_element(StateData, {xmlelement, "failure", [{"xmlns", ?NS_SASL}], [{xmlelement, Error, [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_feature_request, StateData) end; _ -> process_unauthenticated_stanza(StateData, El), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_feature_request, StateData) end; wait_for_sasl_response(timeout, StateData) -> @@ -717,7 +712,7 @@ wait_for_bind({xmlstreamelement, El}, StateData) -> error -> Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), send_element(StateData, Err), - {next_state, wait_for_bind, StateData, ?C2S_OPEN_TIMEOUT}; + fsm_next_state(wait_for_bind, StateData); _ -> JID = jlib:make_jid(U, StateData#state.server, R), Res = IQ#iq{type = result, @@ -727,12 +722,11 @@ wait_for_bind({xmlstreamelement, El}, StateData) -> [{xmlcdata, jlib:jid_to_string(JID)}]}]}]}, send_element(StateData, jlib:iq_to_xml(Res)), - {next_state, wait_for_session, - StateData#state{resource = R, jid = JID}, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_session, + StateData#state{resource = R, jid = JID}) end; _ -> - {next_state, wait_for_bind, StateData, ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_bind, StateData) end; wait_for_bind(timeout, StateData) -> @@ -783,22 +777,22 @@ wait_for_session({xmlstreamelement, El}, StateData) -> privacy_get_user_list, StateData#state.server, none, [U, StateData#state.server]), - {next_state, session_established, - StateData#state{sid = SID, + fsm_next_state(session_established, + StateData#state{ + sid = SID, pres_f = ?SETS:from_list(Fs1), pres_t = ?SETS:from_list(Ts1), - privacy_list = PrivList}}; + privacy_list = PrivList}); _ -> ?INFO_MSG("(~w) Forbidden session for ~s", [StateData#state.socket, jlib:jid_to_string(JID)]), Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), send_element(StateData, Err), - {next_state, wait_for_session, StateData, - ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_session, StateData) end; _ -> - {next_state, wait_for_session, StateData, ?C2S_OPEN_TIMEOUT} + fsm_next_state(wait_for_session, StateData) end; wait_for_session(timeout, StateData) -> @@ -903,7 +897,16 @@ session_established({xmlstreamelement, El}, StateData) -> StateData end end, - {next_state, session_established, NewState}; + fsm_next_state(session_established, NewState); + +%% We hibernate the process to reduce memory consumption after a +%% configurable activity timeout +session_established(timeout, StateData) -> + %% TODO: Options must be stored in state: + Options = [], + proc_lib:hibernate(gen_fsm, enter_loop, + [?MODULE, Options, session_established, StateData]), + fsm_next_state(session_established, StateData); session_established({xmlstreamend, _Name}, StateData) -> send_text(StateData, ?STREAM_TRAILER), @@ -938,7 +941,7 @@ session_established(closed, StateData) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. + fsm_next_state(StateName, StateData). %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 @@ -958,12 +961,11 @@ handle_sync_event({get_presence}, _From, StateName, StateData) -> Resource = StateData#state.resource, Reply = {User, Resource, Show, Status}, - {reply, Reply, StateName, StateData}; + fsm_reply(Reply, StateName, StateData); handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, - {reply, Reply, StateName, StateData}. - + fsm_reply(Reply, StateName, StateData). code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. @@ -976,7 +978,7 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> %%---------------------------------------------------------------------- handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), - {next_state, StateName, StateData}; + fsm_next_state(StateName, StateData); handle_info(replaced, _StateName, StateData) -> Lang = StateData#state.lang, send_text(StateData, @@ -1187,16 +1189,16 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> ejabberd_hooks:run(user_receive_packet, StateData#state.server, [StateData#state.jid, From, To, FixedPacket]), - {next_state, StateName, NewState}; + fsm_next_state(StateName, NewState); true -> - {next_state, StateName, NewState} + fsm_next_state(StateName, NewState) end; handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData) when Monitor == StateData#state.socket_monitor -> {stop, normal, StateData}; handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), - {next_state, StateName, StateData}. + fsm_next_state(StateName, StateData). %%---------------------------------------------------------------------- %% Func: terminate/3 @@ -1858,3 +1860,17 @@ peerip(SockMod, Socket) -> {ok, IPOK} -> IPOK; _ -> undefined end. + +%% fsm_next_state: Generate the next_state FSM tuple with different +%% timeout, depending on the future state +fsm_next_state(session_established, StateData) -> + {next_state, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; +fsm_next_state(StateName, StateData) -> + {next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}. + +%% fsm_reply: Generate the reply FSM tuple with different timeout, +%% depending on the future state +fsm_reply(Reply, session_established, StateData) -> + {reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; +fsm_reply(Reply, StateName, StateData) -> + {reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}.