aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjuhlig <[email protected]>2019-06-21 16:01:22 +0200
committerLoïc Hoguin <[email protected]>2019-06-21 17:10:16 +0200
commite8d6401741021f76988091e6dc633afb65ffbe7b (patch)
treeba4a55fb0a3b7c461cd71986c689db3009af9648
parentae84436f7ceed06a09e3fe1afb30e675579b7621 (diff)
downloadranch-e8d6401741021f76988091e6dc633afb65ffbe7b.tar.gz
ranch-e8d6401741021f76988091e6dc633afb65ffbe7b.tar.bz2
ranch-e8d6401741021f76988091e6dc633afb65ffbe7b.zip
Embedded listeners depending on ranch_server
-rw-r--r--doc/src/manual/ranch.child_spec.asciidoc14
-rw-r--r--ebin/ranch.app2
-rw-r--r--src/ranch.erl10
-rw-r--r--src/ranch_embedded_sup.erl34
-rw-r--r--src/ranch_server_proxy.erl61
-rw-r--r--test/acceptor_SUITE.erl35
-rw-r--r--test/embedded_sup.erl4
7 files changed, 145 insertions, 15 deletions
diff --git a/doc/src/manual/ranch.child_spec.asciidoc b/doc/src/manual/ranch.child_spec.asciidoc
index 9fdf65a..bd6c056 100644
--- a/doc/src/manual/ranch.child_spec.asciidoc
+++ b/doc/src/manual/ranch.child_spec.asciidoc
@@ -16,18 +16,20 @@ child_spec(Ref :: ranch_ref(),
-> supervisor:child_spec()
----
-Build child specifications for a new listener.
+Build child specifications for a new listener which can
+be embedded directly in an application's supervision
+tree.
-This function can be used to embed a listener directly
-in an application's supervision tree.
+The actual listener is placed under a supervisor which
+monitors `ranch_server` via a proxy process and will
+restart the listener if `ranch_server` crashes.
== Arguments
Ref::
The listener name is used to refer to this listener in
-future calls, for example when stopping it or when
-updating the configuration.
+future calls, for example when updating the configuration.
+
It can be any Erlang term. An atom is generally good enough,
for example `api`, `my_app_clear` or `my_app_tls`.
@@ -74,6 +76,8 @@ Child specifications are returned.
== Changelog
+* *2.0*: The actual listener is placed under a supervisor in order to
+ restart the listener if `ranch_server` crashes.
* *2.0*: The `TransOpts` argument must no longer contain
Ranch-specific options if given as a list. Use a map.
* *1.4*: The `NumAcceptors` argument was moved to the transport options.
diff --git a/ebin/ranch.app b/ebin/ranch.app
index 682943b..cf51ced 100644
--- a/ebin/ranch.app
+++ b/ebin/ranch.app
@@ -1,7 +1,7 @@
{application, 'ranch', [
{description, "Socket acceptor pool for TCP protocols."},
{vsn, "1.7.1"},
- {modules, ['ranch','ranch_acceptor','ranch_acceptors_sup','ranch_app','ranch_conns_sup','ranch_conns_sup_sup','ranch_crc32c','ranch_listener_sup','ranch_protocol','ranch_proxy_header','ranch_server','ranch_ssl','ranch_sup','ranch_tcp','ranch_transport']},
+ {modules, ['ranch','ranch_acceptor','ranch_acceptors_sup','ranch_app','ranch_conns_sup','ranch_conns_sup_sup','ranch_crc32c','ranch_embedded_sup','ranch_listener_sup','ranch_protocol','ranch_proxy_header','ranch_server','ranch_server_proxy','ranch_ssl','ranch_sup','ranch_tcp','ranch_transport']},
{registered, [ranch_sup,ranch_server]},
{applications, [kernel,stdlib,ssl]},
{mod, {ranch_app, []}},
diff --git a/src/ranch.erl b/src/ranch.erl
index b26f3a6..0e3541f 100644
--- a/src/ranch.erl
+++ b/src/ranch.erl
@@ -73,8 +73,10 @@ start_listener(Ref, Transport, TransOpts0, Protocol, ProtoOpts)
_ = code:ensure_loaded(Transport),
case {erlang:function_exported(Transport, name, 0), validate_transport_opts(TransOpts)} of
{true, ok} ->
- maybe_started(supervisor:start_child(ranch_sup, child_spec(Ref,
- Transport, TransOpts, Protocol, ProtoOpts)));
+ ChildSpec = #{id => {ranch_listener_sup, Ref}, start => {ranch_listener_sup, start_link, [
+ Ref, Transport, TransOpts, Protocol, ProtoOpts
+ ]}, type => supervisor},
+ maybe_started(supervisor:start_child(ranch_sup, ChildSpec));
{false, _} ->
{error, {bad_transport, Transport}};
{_, TransOptsError} ->
@@ -191,9 +193,9 @@ maybe_resumed(Res) ->
-> supervisor:child_spec().
child_spec(Ref, Transport, TransOpts0, Protocol, ProtoOpts) ->
TransOpts = normalize_opts(TransOpts0),
- {{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
+ #{id => {ranch_embedded_sup, Ref}, start => {ranch_embedded_sup, start_link, [
Ref, Transport, TransOpts, Protocol, ProtoOpts
- ]}, permanent, infinity, supervisor, [ranch_listener_sup]}.
+ ]}, type => supervisor}.
-spec handshake(ref()) -> {ok, ranch_transport:socket()}.
handshake(Ref) ->
diff --git a/src/ranch_embedded_sup.erl b/src/ranch_embedded_sup.erl
new file mode 100644
index 0000000..aa157e0
--- /dev/null
+++ b/src/ranch_embedded_sup.erl
@@ -0,0 +1,34 @@
+%% Copyright (c) 2019, Jan Uhlig <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(ranch_embedded_sup).
+
+-behavior(supervisor).
+
+-export([start_link/5]).
+-export([init/1]).
+
+-spec start_link(ranch:ref(), module(), any(), module(), any())
+ -> {ok, pid()}.
+start_link(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
+ supervisor:start_link(?MODULE, {Ref, Transport, TransOpts, Protocol, ProtoOpts}).
+
+init({Ref, Transport, TransOpts, Protocol, ProtoOpts}) ->
+ Proxy = #{id => ranch_server_proxy,
+ start => {ranch_server_proxy, start_link, []},
+ shutdown => brutal_kill},
+ Listener = #{id => {ranch_listener_sup, Ref},
+ start => {ranch_listener_sup, start_link, [Ref, Transport, TransOpts, Protocol, ProtoOpts]},
+ type => supervisor},
+ {ok, {#{strategy => rest_for_one}, [Proxy, Listener]}}.
diff --git a/src/ranch_server_proxy.erl b/src/ranch_server_proxy.erl
new file mode 100644
index 0000000..949ac33
--- /dev/null
+++ b/src/ranch_server_proxy.erl
@@ -0,0 +1,61 @@
+%% Copyright (c) 2019, Jan Uhlig <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(ranch_server_proxy).
+
+-behavior(gen_server).
+
+-export([start_link/0]).
+-export([init/1]).
+-export([handle_call/3]).
+-export([handle_cast/2]).
+-export([handle_info/2]).
+-export([code_change/3]).
+
+start_link() ->
+ gen_server:start_link(?MODULE, [], []).
+
+init([]) ->
+ case wait_ranch_server(50) of
+ {ok, Monitor} ->
+ {ok, Monitor, hibernate};
+ {error, Reason} ->
+ {stop, Reason}
+ end.
+
+handle_call(_, _, Monitor) ->
+ {noreply, Monitor, hibernate}.
+
+handle_cast(_, Monitor) ->
+ {noreply, Monitor, hibernate}.
+
+handle_info({'DOWN', Monitor, process, _, Reason}, Monitor) ->
+ {stop, Reason, Monitor};
+handle_info(_, Monitor) ->
+ {noreply, Monitor, hibernate}.
+
+code_change(_, Monitor, _) ->
+ {ok, Monitor}.
+
+wait_ranch_server(N) ->
+ case whereis(ranch_server) of
+ undefined when N > 0 ->
+ receive after 100 -> ok end,
+ wait_ranch_server(N - 1);
+ undefined ->
+ {error, noproc};
+ Pid ->
+ Monitor = monitor(process, Pid),
+ {ok, Monitor}
+ end.
diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl
index 294b07e..332967f 100644
--- a/test/acceptor_SUITE.erl
+++ b/test/acceptor_SUITE.erl
@@ -85,6 +85,7 @@ groups() ->
supervisor_clean_child_restart,
supervisor_clean_restart,
supervisor_conns_alive,
+ supervisor_embedded_ranch_server_crash,
supervisor_protocol_start_link_crash,
supervisor_server_recover_state,
supervisor_unexpected_message
@@ -190,20 +191,26 @@ misc_info_embedded(_) ->
doc("Information about listeners in embedded mode."),
{ok, SupPid} = embedded_sup:start_link(),
%% Open a listener with a few connections.
- {ok, Pid1} = embedded_sup:start_listener(SupPid, {misc_info_embedded, tcp},
+ {ok, EmbeddedSupPid1} = embedded_sup:start_listener(SupPid, {misc_info_embedded, tcp},
ranch_tcp, #{num_acceptors => 1},
remove_conn_and_wait_protocol, [{remove, true, 2500}]),
+ {_, Pid1, _, _} = lists:keyfind({ranch_listener_sup, {misc_info_embedded, tcp}}, 1,
+ supervisor:which_children(EmbeddedSupPid1)),
Port1 = ranch:get_port({misc_info_embedded, tcp}),
%% Open a few more listeners with different arguments.
- {ok, Pid2} = embedded_sup:start_listener(SupPid, {misc_info_embedded, act},
+ {ok, EmbeddedSupPid2} = embedded_sup:start_listener(SupPid, {misc_info_embedded, act},
ranch_tcp, #{num_acceptors => 2},
active_echo_protocol, {}),
+ {_, Pid2, _, _} = lists:keyfind({ranch_listener_sup, {misc_info_embedded, act}}, 1,
+ supervisor:which_children(EmbeddedSupPid2)),
Port2 = ranch:get_port({misc_info_embedded, act}),
ranch:set_max_connections({misc_info_embedded, act}, infinity),
Opts = ct_helper:get_certs_from_ets(),
- {ok, Pid3} = embedded_sup:start_listener(SupPid, {misc_info_embedded, ssl},
+ {ok, EmbeddedSupPid3} = embedded_sup:start_listener(SupPid, {misc_info_embedded, ssl},
ranch_ssl, #{num_acceptors => 3, socket_opts => Opts},
echo_protocol, [{}]),
+ {_, Pid3, _, _} = lists:keyfind({ranch_listener_sup, {misc_info_embedded, ssl}}, 1,
+ supervisor:which_children(EmbeddedSupPid3)),
Port3 = ranch:get_port({misc_info_embedded, ssl}),
%% Open 5 connections, 3 removed from the count.
{ok, _} = gen_tcp:connect("localhost", Port1, [binary, {active, false}, {packet, raw}]),
@@ -1352,6 +1359,28 @@ do_supervisor_conns_alive(_) ->
ok = clean_traces(),
ok = ranch:stop_listener(Name).
+supervisor_embedded_ranch_server_crash(_) ->
+ doc("Ensure that restarting ranch_server also restarts embedded listeners."),
+ Name = name(),
+ {ok, SupPid} = embedded_sup:start_link(),
+ {ok, EmbeddedSupPid} = embedded_sup:start_listener(SupPid, Name,
+ ranch_tcp, #{},
+ echo_protocol, []),
+ [{{ranch_listener_sup, Name}, ListenerPid, supervisor, _},
+ {ranch_server_proxy, ProxyPid, worker, _}] = supervisor:which_children(EmbeddedSupPid),
+ ProxyMonitor = monitor(process, ProxyPid),
+ ListenerMonitor = monitor(process, ListenerPid),
+ ok = supervisor:terminate_child(ranch_sup, ranch_server),
+ receive {'DOWN', ProxyMonitor, process, ProxyPid, shutdown} -> ok after 1000 -> exit(timeout) end,
+ receive {'DOWN', ListenerMonitor, process, ListenerPid, shutdown} -> ok after 1000 -> exit(timeout) end,
+ {ok, _} = supervisor:restart_child(ranch_sup, ranch_server),
+ receive after 1000 -> ok end,
+ [{{ranch_listener_sup, Name}, _, supervisor, _},
+ {ranch_server_proxy, _, worker, _}] = supervisor:which_children(EmbeddedSupPid),
+ embedded_sup:stop_listener(SupPid, Name),
+ embedded_sup:stop(SupPid),
+ ok.
+
supervisor_protocol_start_link_crash(_) ->
doc("Ensure a protocol start crash does not kill all connections."),
Name = name(),
diff --git a/test/embedded_sup.erl b/test/embedded_sup.erl
index 52275e0..49f2d0c 100644
--- a/test/embedded_sup.erl
+++ b/test/embedded_sup.erl
@@ -23,6 +23,6 @@ start_listener(SupPid, Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
).
stop_listener(SupPid, Ref) ->
- ok = supervisor:terminate_child(SupPid, {ranch_listener_sup, Ref}),
- ok = supervisor:delete_child(SupPid, {ranch_listener_sup, Ref}),
+ ok = supervisor:terminate_child(SupPid, {ranch_embedded_sup, Ref}),
+ ok = supervisor:delete_child(SupPid, {ranch_embedded_sup, Ref}),
ranch_server:cleanup_listener_opts(Ref).