aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndreas Köhler <andreas.koehler@1und1.de>2010-11-02 14:43:03 +0100
committerBadlop <badlop@process-one.net>2010-11-10 23:46:16 +0100
commit1f1d2bd5f52ce6376ad2d9f577da5323b1582a9c (patch)
treeacb7a444108a6c13a9cb9a80361a892c814ac9b3 /src
parentIn mod_last*:get_last_iq/4, check for user resources first to return 0 second... (diff)
Bind listener ports early and start accepting connections later
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.
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_listener.erl100
1 files changed, 69 insertions, 31 deletions
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index a179dfda4..b41b97dc1 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -48,8 +48,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 ->
@@ -99,15 +122,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 ->
@@ -128,28 +143,39 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
end.
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
- 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} ->
- %% 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}
@@ -194,6 +220,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) ->