aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorj.uhlig <[email protected]>2018-04-09 12:53:02 +0200
committerLoïc Hoguin <[email protected]>2018-05-02 17:21:11 +0200
commit7006c50c3ed6c3cbcb24e9e88a76ebd1aaf3a5f8 (patch)
tree4f34510579b6bff962e1906925b951504217f137
parente2d8d737677f464bd18a10b630417b770caa01cc (diff)
downloadranch-7006c50c3ed6c3cbcb24e9e88a76ebd1aaf3a5f8.tar.gz
ranch-7006c50c3ed6c3cbcb24e9e88a76ebd1aaf3a5f8.tar.bz2
ranch-7006c50c3ed6c3cbcb24e9e88a76ebd1aaf3a5f8.zip
Add suspend/resume of listeners and update of transport options
This allows graceful draining of connections, updating transport options on a running listener without having to drop connections and other similar scenarios. Note that when updating transport options the listener must be suspended which means that new connections will be rejected until the listener is resumed.
-rw-r--r--doc/src/guide/listeners.asciidoc52
-rw-r--r--doc/src/manual/ranch.asciidoc46
-rw-r--r--src/ranch.erl81
-rw-r--r--src/ranch_acceptors_sup.erl11
-rw-r--r--src/ranch_listener_sup.erl4
-rw-r--r--src/ranch_server.erl41
-rw-r--r--test/acceptor_SUITE.erl85
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(),