Browse Source

Embedded listeners depending on ranch_server

juhlig 6 years ago
parent
commit
e8d6401741

+ 9 - 5
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.

+ 1 - 1
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, []}},

+ 6 - 4
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) ->

+ 34 - 0
src/ranch_embedded_sup.erl

@@ -0,0 +1,34 @@
+%% Copyright (c) 2019, Jan Uhlig <ju@mailingwork.de>
+%%
+%% 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]}}.

+ 61 - 0
src/ranch_server_proxy.erl

@@ -0,0 +1,61 @@
+%% Copyright (c) 2019, Jan Uhlig <ju@mailingwork.de>
+%%
+%% 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.

+ 32 - 3
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(),

+ 2 - 2
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).