diff options
-rw-r--r-- | doc/src/guide/listeners.asciidoc | 52 | ||||
-rw-r--r-- | doc/src/manual/ranch.asciidoc | 46 | ||||
-rw-r--r-- | src/ranch.erl | 81 | ||||
-rw-r--r-- | src/ranch_acceptors_sup.erl | 11 | ||||
-rw-r--r-- | src/ranch_listener_sup.erl | 4 | ||||
-rw-r--r-- | src/ranch_server.erl | 41 | ||||
-rw-r--r-- | test/acceptor_SUITE.erl | 85 |
7 files changed, 296 insertions, 24 deletions
diff --git a/doc/src/guide/listeners.asciidoc b/doc/src/guide/listeners.asciidoc index 97afa22..21a6838 100644 --- a/doc/src/guide/listeners.asciidoc +++ b/doc/src/guide/listeners.asciidoc @@ -91,6 +91,34 @@ named `tcp_echo`. We can now stop it. [source,erlang] ranch:stop_listener(tcp_echo). +=== Suspending and resuming a listener + +Listeners can be suspended and resumed by calling +`ranch:suspend_listener/1` and `ranch:resume_listener/1`, +respectively, with the name of the listener as argument. + +Suspending a listener will cause it to stop listening and not accept +new connections, but existing connection processes will not be stopped. + +.Suspending a listener + +[source,erlang] +ranch:suspend_listener(tcp_echo). + +Resuming a listener will cause it to start listening and accept new +connections again. +It is worth mentioning, however, that if the listener is configured +to listen on a random port, it will listen on a different port than +before it was suspended. + +.Resuming a listener + +[source,erlang] +ranch:resume_listener(tcp_echo). + +Whether a listener is currently running or suspended can be queried +by calling `ranch:get_status/1` with the listener name as argument. + === Default transport options By default the socket will be set to return `binary` data, with the @@ -296,6 +324,30 @@ calling `ranch:get_protocol_options/1`. [source,erlang] Opts = ranch:get_protocol_options(tcp_echo). +=== Changing transport options + +Ranch allows you to change the transport options of a listener, for +example to make it listen on a different port. + +To change transport options, the listener has to be suspended first. +Then you are allowed to change the transport options by calling +`ranch:set_transport_options/2` with the listener name and the new +transport options as arguments. +After that, you can resume the listener. + +.Changing the transport options + +[source,erlang] +ranch:set_transport_options(tcp_echo, NewOpts). + +You can retrieve the current transport options by calling +`ranch:get_transport_options/1`. + +.Retrieving the current transport options + +[source,erlang] +Opts = ranch:get_transport_options(tcp_echo). + === Obtaining information about listeners Ranch provides two functions for retrieving information about the diff --git a/doc/src/manual/ranch.asciidoc b/doc/src/manual/ranch.asciidoc index e0244c8..b442f88 100644 --- a/doc/src/manual/ranch.asciidoc +++ b/doc/src/manual/ranch.asciidoc @@ -114,6 +114,19 @@ ProtoOpts = any():: Current protocol options. Return the protocol options set for the given listener. +=== get_status(Ref) -> running | suspended + +Ref = ref():: Listener name. + +Return the status of the given listener. + +=== get_transport_options(Ref) -> ProtoOpts + +Ref = ref():: Listener name. +TransOpts = any():: Current transport options. + +Return the transport options set for the given listener. + === info() -> [{Ref, [{Key, Value}]}] Ref = ref():: Listener name. @@ -155,6 +168,16 @@ without sacrificing the latency of the system. This function may only be called from a connection process. +=== resume_listener(Ref) -> ok + +Ref = ref():: Listener name. + +Resume the given listener if it is suspended. +If the listener is already running, nothing will happen. + +The listener will be started with the transport options +currently set for it. + === set_max_connections(Ref, MaxConns) -> ok Ref = ref():: Listener name. @@ -176,6 +199,18 @@ Set the protocol options for the given listener. The change will be applied immediately for all new connections. Old connections will not receive the new options. +=== set_transport_options(Ref, TransOpts) -> ok | {error, running} + +Ref = ref():: Listener name. +ProtoOpts = any():: New transport options. + +Set the transport options for the given listener. + +The listener must be suspended for this call to succeed. +If the listener is running, `{error, running}` will be returned. + +The change will take effect when the listener is being resumed. + === start_listener(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> {ok, pid()} | {error, badarg} Ref = ref():: Listener name. @@ -207,3 +242,14 @@ connection processes or give them some time to stop properly. This function does not return until the listener is completely stopped. + +=== suspend_listener(Ref) -> ok + +Ref = ref():: Listener name. + +Suspend the given listener if it is running. +If the listener is already suspended, nothing will happen. + +The listener will stop listening and accepting connections by +closing the listening port, but will not stop running connection +processes. diff --git a/src/ranch.erl b/src/ranch.erl index 70fb64c..5af4b74 100644 --- a/src/ranch.erl +++ b/src/ranch.erl @@ -17,17 +17,23 @@ -export([start_listener/5]). -export([start_listener/6]). -export([stop_listener/1]). +-export([suspend_listener/1]). +-export([resume_listener/1]). -export([child_spec/5]). -export([child_spec/6]). -export([accept_ack/1]). -export([remove_connection/1]). +-export([get_status/1]). -export([get_addr/1]). -export([get_port/1]). -export([get_max_connections/1]). -export([set_max_connections/2]). +-export([get_transport_options/1]). +-export([set_transport_options/2]). -export([get_protocol_options/1]). -export([set_protocol_options/2]). -export([info/0]). +-export([info/1]). -export([procs/2]). -export([filter_options/3]). -export([set_option_default/3]). @@ -110,6 +116,37 @@ stop_listener(Ref) -> {error, Reason} end. +-spec suspend_listener(ref()) -> ok | {error, term()}. +suspend_listener(Ref) -> + case get_status(Ref) of + running -> + ListenerSup = ranch_server:get_listener_sup(Ref), + ok = ranch_server:set_addr(Ref, {undefined, undefined}), + supervisor:terminate_child(ListenerSup, ranch_acceptors_sup); + suspended -> + ok + end. + +-spec resume_listener(ref()) -> ok | {error, term()}. +resume_listener(Ref) -> + case get_status(Ref) of + running -> + ok; + suspended -> + ListenerSup = ranch_server:get_listener_sup(Ref), + Res = supervisor:restart_child(ListenerSup, ranch_acceptors_sup), + maybe_resumed(Res) + end. + +maybe_resumed(Error={error, {listen_error, _, Reason}}) -> + start_error(Reason, Error); +maybe_resumed({ok, _}) -> + ok; +maybe_resumed({ok, _, _}) -> + ok; +maybe_resumed(Res) -> + Res. + -spec child_spec(ref(), module(), any(), module(), any()) -> supervisor:child_spec(). child_spec(Ref, Transport, TransOpts, Protocol, ProtoOpts) -> @@ -137,11 +174,22 @@ remove_connection(Ref) -> ConnsSup ! {remove_connection, Ref, self()}, ok. --spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()}. +-spec get_status(ref()) -> running | suspended | restarting. +get_status(Ref) -> + ListenerSup = ranch_server:get_listener_sup(Ref), + Children = supervisor:which_children(ListenerSup), + case lists:keyfind(ranch_acceptors_sup, 1, Children) of + {_, undefined, _, _} -> + suspended; + {_, AcceptorsSup, _, _} when is_pid(AcceptorsSup) -> + running + end. + +-spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()} | {undefined, undefined}. get_addr(Ref) -> ranch_server:get_addr(Ref). --spec get_port(ref()) -> inet:port_number(). +-spec get_port(ref()) -> inet:port_number() | undefined. get_port(Ref) -> {_, Port} = get_addr(Ref), Port. @@ -154,6 +202,19 @@ get_max_connections(Ref) -> set_max_connections(Ref, MaxConnections) -> ranch_server:set_max_connections(Ref, MaxConnections). +-spec get_transport_options(ref()) -> any(). +get_transport_options(Ref) -> + ranch_server:get_transport_options(Ref). + +-spec set_transport_options(ref(), any()) -> ok | {error, running}. +set_transport_options(Ref, TransOpts) -> + case get_status(Ref) of + suspended -> + ok = ranch_server:set_transport_options(Ref, TransOpts); + running -> + {error, running} + end. + -spec get_protocol_options(ref()) -> any(). get_protocol_options(Ref) -> ranch_server:get_protocol_options(Ref). @@ -167,14 +228,22 @@ info() -> [{Ref, listener_info(Ref, Pid)} || {Ref, Pid} <- ranch_server:get_listener_sups()]. +-spec info(ref()) -> [{atom(), any()}]. +info(Ref) -> + Pid = ranch_server:get_listener_sup(Ref), + listener_info(Ref, Pid). + listener_info(Ref, Pid) -> - [_, NumAcceptors, Transport, TransOpts, Protocol, _] = ranch_server:get_listener_start_args(Ref), + [_, NumAcceptors, Transport, _, Protocol, _] = ranch_server:get_listener_start_args(Ref), ConnsSup = ranch_server:get_connections_sup(Ref), + Status = get_status(Ref), {IP, Port} = get_addr(Ref), MaxConns = get_max_connections(Ref), + TransOpts = ranch_server:get_transport_options(Ref), ProtoOpts = get_protocol_options(Ref), [ {pid, Pid}, + {status, Status}, {ip, IP}, {port, Port}, {num_acceptors, NumAcceptors}, @@ -197,7 +266,11 @@ procs1(Ref, Sup) -> ListenerSup = ranch_server:get_listener_sup(Ref), {_, SupPid, _, _} = lists:keyfind(Sup, 1, supervisor:which_children(ListenerSup)), - [Pid || {_, Pid, _, _} <- supervisor:which_children(SupPid)]. + try + [Pid || {_, Pid, _, _} <- supervisor:which_children(SupPid)] + catch exit:{noproc, _} when Sup =:= ranch_acceptors_sup -> + [] + end. -spec filter_options([inet | inet6 | {atom(), any()} | {raw, any(), any(), any()}], [atom()], Acc) -> Acc when Acc :: [any()]. diff --git a/src/ranch_acceptors_sup.erl b/src/ranch_acceptors_sup.erl index f4b2fd8..9e39f2d 100644 --- a/src/ranch_acceptors_sup.erl +++ b/src/ranch_acceptors_sup.erl @@ -15,16 +15,17 @@ -module(ranch_acceptors_sup). -behaviour(supervisor). --export([start_link/4]). +-export([start_link/3]). -export([init/1]). --spec start_link(ranch:ref(), non_neg_integer(), module(), any()) +-spec start_link(ranch:ref(), non_neg_integer(), module()) -> {ok, pid()}. -start_link(Ref, NumAcceptors, Transport, TransOpts) -> - supervisor:start_link(?MODULE, [Ref, NumAcceptors, Transport, TransOpts]). +start_link(Ref, NumAcceptors, Transport) -> + supervisor:start_link(?MODULE, [Ref, NumAcceptors, Transport]). -init([Ref, NumAcceptors, Transport, TransOpts]) -> +init([Ref, NumAcceptors, Transport]) -> ConnsSup = ranch_server:get_connections_sup(Ref), + TransOpts = ranch_server:get_transport_options(Ref), LSocket = case proplists:get_value(socket, TransOpts) of undefined -> TransOpts2 = proplists:delete(ack_timeout, diff --git a/src/ranch_listener_sup.erl b/src/ranch_listener_sup.erl index d3bc59d..502df44 100644 --- a/src/ranch_listener_sup.erl +++ b/src/ranch_listener_sup.erl @@ -22,7 +22,7 @@ -> {ok, pid()}. start_link(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> MaxConns = proplists:get_value(max_connections, TransOpts, 1024), - ranch_server:set_new_listener_opts(Ref, MaxConns, ProtoOpts, + ranch_server:set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts, [Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts]), supervisor:start_link(?MODULE, { Ref, NumAcceptors, Transport, TransOpts, Protocol @@ -38,7 +38,7 @@ init({Ref, NumAcceptors, Transport, TransOpts, Protocol}) -> [Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol]}, permanent, infinity, supervisor, [ranch_conns_sup]}, {ranch_acceptors_sup, {ranch_acceptors_sup, start_link, - [Ref, NumAcceptors, Transport, TransOpts]}, + [Ref, NumAcceptors, Transport]}, permanent, infinity, supervisor, [ranch_acceptors_sup]} ], {ok, {{rest_for_one, 1, 5}, ChildSpecs}}. diff --git a/src/ranch_server.erl b/src/ranch_server.erl index 89c508c..80f82d6 100644 --- a/src/ranch_server.erl +++ b/src/ranch_server.erl @@ -17,7 +17,7 @@ %% API. -export([start_link/0]). --export([set_new_listener_opts/4]). +-export([set_new_listener_opts/5]). -export([cleanup_listener_opts/1]). -export([set_connections_sup/2]). -export([get_connections_sup/1]). @@ -29,6 +29,8 @@ -export([get_addr/1]). -export([set_max_connections/2]). -export([get_max_connections/1]). +-export([set_transport_options/2]). +-export([get_transport_options/1]). -export([set_protocol_options/2]). -export([get_protocol_options/1]). -export([get_listener_start_args/1]). @@ -55,15 +57,16 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). --spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any(), [any()]) -> ok. -set_new_listener_opts(Ref, MaxConns, ProtoOpts, StartArgs) -> - gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, ProtoOpts, StartArgs}). +-spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any(), any(), [any()]) -> ok. +set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts, StartArgs) -> + gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, TransOpts, ProtoOpts, StartArgs}). -spec cleanup_listener_opts(ranch:ref()) -> ok. cleanup_listener_opts(Ref) -> _ = ets:delete(?TAB, {addr, Ref}), _ = ets:delete(?TAB, {max_conns, Ref}), - _ = ets:delete(?TAB, {opts, Ref}), + _ = ets:delete(?TAB, {trans_opts, Ref}), + _ = ets:delete(?TAB, {proto_opts, Ref}), _ = ets:delete(?TAB, {listener_start_args, Ref}), %% We also remove the pid of the connections supervisor. %% Depending on the timing, it might already have been deleted @@ -103,11 +106,11 @@ get_listener_sup(Ref) -> get_listener_sups() -> [{Ref, Pid} || [Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})]. --spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()}) -> ok. +-spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()} | {undefined, undefined}) -> ok. set_addr(Ref, Addr) -> gen_server:call(?MODULE, {set_addr, Ref, Addr}). --spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()}. +-spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()} | {undefined, undefined}. get_addr(Ref) -> ets:lookup_element(?TAB, {addr, Ref}, 2). @@ -119,13 +122,21 @@ set_max_connections(Ref, MaxConnections) -> get_max_connections(Ref) -> ets:lookup_element(?TAB, {max_conns, Ref}, 2). +-spec set_transport_options(ranch:ref(), any()) -> ok. +set_transport_options(Ref, TransOpts) -> + gen_server:call(?MODULE, {set_trans_opts, Ref, TransOpts}). + +-spec get_transport_options(ranch:ref()) -> any(). +get_transport_options(Ref) -> + ets:lookup_element(?TAB, {trans_opts, Ref}, 2). + -spec set_protocol_options(ranch:ref(), any()) -> ok. set_protocol_options(Ref, ProtoOpts) -> - gen_server:call(?MODULE, {set_opts, Ref, ProtoOpts}). + gen_server:call(?MODULE, {set_proto_opts, Ref, ProtoOpts}). -spec get_protocol_options(ranch:ref()) -> any(). get_protocol_options(Ref) -> - ets:lookup_element(?TAB, {opts, Ref}, 2). + ets:lookup_element(?TAB, {proto_opts, Ref}, 2). -spec get_listener_start_args(ranch:ref()) -> [any()]. get_listener_start_args(Ref) -> @@ -144,9 +155,10 @@ init([]) -> [Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})], {ok, #state{monitors=ConnMonitors++ListenerMonitors}}. -handle_call({set_new_listener_opts, Ref, MaxConns, ProtoOpts, StartArgs}, _, State) -> +handle_call({set_new_listener_opts, Ref, MaxConns, TransOpts, ProtoOpts, StartArgs}, _, State) -> ets:insert(?TAB, {{max_conns, Ref}, MaxConns}), - ets:insert(?TAB, {{opts, Ref}, ProtoOpts}), + ets:insert(?TAB, {{trans_opts, Ref}, TransOpts}), + ets:insert(?TAB, {{proto_opts, Ref}, ProtoOpts}), ets:insert(?TAB, {{listener_start_args, Ref}, StartArgs}), {reply, ok, State}; handle_call({set_connections_sup, Ref, Pid}, _, @@ -177,8 +189,11 @@ handle_call({set_max_conns, Ref, MaxConns}, _, State) -> ConnsSup = get_connections_sup(Ref), ConnsSup ! {set_max_conns, MaxConns}, {reply, ok, State}; -handle_call({set_opts, Ref, Opts}, _, State) -> - ets:insert(?TAB, {{opts, Ref}, Opts}), +handle_call({set_trans_opts, Ref, Opts}, _, State) -> + ets:insert(?TAB, {{trans_opts, Ref}, Opts}), + {reply, ok, State}; +handle_call({set_proto_opts, Ref, Opts}, _, State) -> + ets:insert(?TAB, {{proto_opts, Ref}, Opts}), ConnsSup = get_connections_sup(Ref), ConnsSup ! {set_opts, Opts}, {reply, ok, State}; diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl index a8c6d23..e441b51 100644 --- a/test/acceptor_SUITE.erl +++ b/test/acceptor_SUITE.erl @@ -28,6 +28,7 @@ groups() -> tcp_accept_socket, tcp_active_echo, tcp_echo, + tcp_graceful, tcp_inherit_options, tcp_max_connections, tcp_max_connections_and_beyond, @@ -45,6 +46,7 @@ groups() -> ssl_accept_socket, ssl_active_echo, ssl_echo, + ssl_graceful, ssl_sni_echo, ssl_sni_fail, ssl_getopts_capability, @@ -113,6 +115,7 @@ misc_info(_) -> [ {{misc_info, act}, [ {pid, Pid2}, + {status, _}, {ip, _}, {port, Port2}, {num_acceptors, 2}, @@ -126,6 +129,7 @@ misc_info(_) -> ]}, {{misc_info, ssl}, [ {pid, Pid3}, + {status, _}, {ip, _}, {port, Port3}, {num_acceptors, 3}, @@ -139,6 +143,7 @@ misc_info(_) -> ]}, {{misc_info, tcp}, [ {pid, Pid1}, + {status, _}, {ip, _}, {port, Port1}, {num_acceptors, 1}, @@ -189,6 +194,7 @@ misc_info_embedded(_) -> [ {{misc_info_embedded, act}, [ {pid, Pid2}, + {status, _}, {ip, _}, {port, Port2}, {num_acceptors, 2}, @@ -202,6 +208,7 @@ misc_info_embedded(_) -> ]}, {{misc_info_embedded, ssl}, [ {pid, Pid3}, + {status, _}, {ip, _}, {port, Port3}, {num_acceptors, 3}, @@ -215,6 +222,7 @@ misc_info_embedded(_) -> ]}, {{misc_info_embedded, tcp}, [ {pid, Pid1}, + {status, _}, {ip, _}, {port, Port1}, {num_acceptors, 1}, @@ -364,6 +372,45 @@ do_ssl_sni_fail() -> {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. +ssl_graceful(_) -> + doc("Ensure suspending and resuming of listeners does not kill active connections."), + Name = name(), + Opts = ct_helper:get_certs_from_ets(), + {ok, _} = ranch:start_listener(Name, ranch_ssl, Opts, echo_protocol, []), + Port = ranch:get_port(Name), + %% Make sure connections with a fresh listener work. + running = ranch:get_status(Name), + {ok, Socket1} = ssl:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = ssl:send(Socket1, <<"SSL with fresh listener">>), + {ok, <<"SSL with fresh listener">>} = ssl:recv(Socket1, 23, 1000), + %% Make sure transport options cannot be changed on a running listener. + {error, running} = ranch:set_transport_options(Name, [{port, Port}|Opts]), + %% Suspend listener, make sure established connections keep running. + ok = ranch:suspend_listener(Name), + suspended = ranch:get_status(Name), + ok = ssl:send(Socket1, <<"SSL with suspended listener">>), + {ok, <<"SSL with suspended listener">>} = ssl:recv(Socket1, 27, 1000), + %% Make sure new connections are refused on the suspended listener. + {error, econnrefused} = ssl:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + %% Make sure transport options can be changed when listener is suspended. + ok = ranch:set_transport_options(Name, [{port, Port}|Opts]), + %% Resume listener, make sure connections can be established again. + ok = ranch:resume_listener(Name), + running = ranch:get_status(Name), + {ok, Socket2} = ssl:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = ssl:send(Socket2, <<"SSL with resumed listener">>), + {ok, <<"SSL with resumed listener">>} = ssl:recv(Socket2, 25, 1000), + %% Make sure transport options cannot be changed on resumed listener. + {error, running} = ranch:set_transport_options(Name, [{port, Port}|Opts]), + ok = ranch:stop_listener(Name), + {error, closed} = ssl:recv(Socket1, 0, 1000), + {error, closed} = ssl:recv(Socket2, 0, 1000), + {'EXIT', _} = begin catch ranch:get_port(Name) end, + ok. + ssl_getopts_capability(_) -> doc("Ensure getopts/2 capability."), Name=name(), @@ -478,6 +525,44 @@ tcp_echo(_) -> {'EXIT', _} = begin catch ranch:get_port(Name) end, ok. +tcp_graceful(_) -> + doc("Ensure suspending and resuming of listeners does not kill active connections."), + Name = name(), + {ok, _} = ranch:start_listener(Name, ranch_tcp, [], echo_protocol, []), + Port = ranch:get_port(Name), + %% Make sure connections with a fresh listener work. + running = ranch:get_status(Name), + {ok, Socket1} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket1, <<"TCP with fresh listener">>), + {ok, <<"TCP with fresh listener">>} = gen_tcp:recv(Socket1, 23, 1000), + %% Make sure transport options cannot be changed on a running listener. + {error, running} = ranch:set_transport_options(Name, [{port, Port}]), + %% Suspend listener, make sure established connections keep running. + ok = ranch:suspend_listener(Name), + suspended = ranch:get_status(Name), + ok = gen_tcp:send(Socket1, <<"TCP with suspended listener">>), + {ok, <<"TCP with suspended listener">>} = gen_tcp:recv(Socket1, 27, 1000), + %% Make sure new connections are refused on the suspended listener. + {error, econnrefused} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + %% Make sure transport options can be changed when listener is suspended. + ok = ranch:set_transport_options(Name, [{port, Port}]), + %% Resume listener, make sure connections can be established again. + ok = ranch:resume_listener(Name), + running = ranch:get_status(Name), + {ok, Socket2} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket2, <<"TCP with resumed listener">>), + {ok, <<"TCP with resumed listener">>} = gen_tcp:recv(Socket2, 25, 1000), + %% Make sure transport options cannot be changed on resumed listener. + {error, running} = ranch:set_transport_options(Name, [{port, Port}]), + ok = ranch:stop_listener(Name), + {error, closed} = gen_tcp:recv(Socket1, 0, 1000), + {error, closed} = gen_tcp:recv(Socket2, 0, 1000), + {'EXIT', _} = begin catch ranch:get_port(Name) end, + ok. + tcp_inherit_options(_) -> doc("Ensure TCP options are inherited in the protocol."), Name = name(), |