From e8d6401741021f76988091e6dc633afb65ffbe7b Mon Sep 17 00:00:00 2001 From: juhlig Date: Fri, 21 Jun 2019 16:01:22 +0200 Subject: Embedded listeners depending on ranch_server --- doc/src/manual/ranch.child_spec.asciidoc | 14 +++++--- ebin/ranch.app | 2 +- src/ranch.erl | 10 +++--- src/ranch_embedded_sup.erl | 34 ++++++++++++++++++ src/ranch_server_proxy.erl | 61 ++++++++++++++++++++++++++++++++ test/acceptor_SUITE.erl | 35 ++++++++++++++++-- test/embedded_sup.erl | 4 +-- 7 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 src/ranch_embedded_sup.erl create mode 100644 src/ranch_server_proxy.erl 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 +%% +%% 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 +%% +%% 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). -- cgit v1.2.3