From 1ab92d11594a713baf81b231df84f59f6f96dfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=B6hler?= Date: Tue, 2 Nov 2010 14:43:03 +0100 Subject: [PATCH] Bind listener ports early and start accepting connections later (EJAB-1334) It may happen that auth or rdbms client tcp connections bind a local socket to a port number required by a configered listener. The ejabberd applications fails to start up and needs to be restarted. In plain C you would bind(2) the listener port and listen(2) later on. gen_tcp:listen/2 does not allow to separate these two steps though, so another way is not to accept connections while start up. OTOH, the kernel will syn/ack incoming connections and receive data, leaving them in a buffer for the ejabberd to read from. If this is unwanted, a load balancer would need to receive data from the ejabberd server before adding the node to its pool. This patch binds tcp ports while initializing the ejabberd_listener process, storing ListenSockets in an ets table. start_listeners/0 will reuse these ports later on. --- src/ejabberd_listener.erl | 98 +++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index c16686159..dbe4d292e 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -49,8 +49,31 @@ start_link() -> init(_) -> + ets:new(listen_sockets, [named_table, public]), + bind_tcp_ports(), {ok, {{one_for_one, 10, 1}, []}}. +bind_tcp_ports() -> + case ejabberd_config:get_local_option(listen) of + undefined -> + ignore; + Ls -> + lists:foreach( + fun({Port, Module, Opts}) -> + bind_tcp_port(Port, Module, Opts) + end, Ls) + end. + +bind_tcp_port(PortIP, Module, RawOpts) -> + {Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts), + {_Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean), + case Proto of + udp -> ok; + _ -> + ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS), + ets:insert(listen_sockets, {PortIP, ListenSocket}) + end. + start_listeners() -> case ejabberd_config:get_local_option(listen) of undefined -> @@ -100,15 +123,7 @@ start_dependent(Port, Module, Opts) -> init(PortIP, Module, RawOpts) -> {Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts), - %% The first inet|inet6 and the last {ip, _} work, - %% so overriding those in Opts - Opts = [IPV | OptsClean] ++ [{ip, IPT}], - SockOpts = lists:filter(fun({ip, _}) -> true; - (inet6) -> true; - (inet) -> true; - ({backlog, _}) -> true; - (_) -> false - end, Opts), + {Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean), if Proto == udp -> init_udp(PortIP, Module, Opts, SockOpts, Port, IPS); true -> @@ -129,26 +144,39 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) -> end. init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) -> - SockOpts2 = case erlang:system_info(otp_release) >= "R13B" of - true -> [{send_timeout_close, true} | SockOpts]; - false -> SockOpts - end, - Res = gen_tcp:listen(Port, [binary, - {packet, 0}, - {active, false}, - {reuseaddr, true}, - {nodelay, true}, - {send_timeout, ?TCP_SEND_TIMEOUT}, - {keepalive, true} | - SockOpts2]), - case Res of - {ok, ListenSocket} -> - %% Inform my parent that this port was opened succesfully - proc_lib:init_ack({ok, self()}), - %% And now start accepting connection attempts - accept(ListenSocket, Module, Opts); - {error, Reason} -> - socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) + ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS), + %% Inform my parent that this port was opened succesfully + proc_lib:init_ack({ok, self()}), + %% And now start accepting connection attempts + accept(ListenSocket, Module, Opts). + +listen_tcp(PortIP, Module, SockOpts, Port, IPS) -> + case ets:lookup(listen_sockets, PortIP) of + [{PortIP, ListenSocket}] -> + ?INFO_MSG("Reusing listening port for ~p", [Port]), + ets:delete(listen_sockets, Port), + ListenSocket; + _ -> + SockOpts2 = try erlang:system_info(otp_release) >= "R13B" of + true -> [{send_timeout_close, true} | SockOpts]; + false -> SockOpts + catch + _:_ -> [] + end, + Res = gen_tcp:listen(Port, [binary, + {packet, 0}, + {active, false}, + {reuseaddr, true}, + {nodelay, true}, + {send_timeout, ?TCP_SEND_TIMEOUT}, + {keepalive, true} | + SockOpts2]), + case Res of + {ok, ListenSocket} -> + ListenSocket; + {error, Reason} -> + socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) + end end. %% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean} @@ -193,6 +221,18 @@ parse_listener_portip(PortIP, Opts) -> end, {Port, IPT, IPS, IPV, Proto, OptsClean}. +prepare_opts(IPT, IPV, OptsClean) -> + %% The first inet|inet6 and the last {ip, _} work, + %% so overriding those in Opts + Opts = [IPV | OptsClean] ++ [{ip, IPT}], + SockOpts = lists:filter(fun({ip, _}) -> true; + (inet6) -> true; + (inet) -> true; + ({backlog, _}) -> true; + (_) -> false + end, Opts), + {Opts, SockOpts}. + add_proto(Port, Opts) when is_integer(Port) -> {Port, get_proto(Opts)}; add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->