Browse Source

Enable usage of experimental inet_backend option for TCP listeners

Maria Scott 3 years ago
parent
commit
3303dac8ba
7 changed files with 236 additions and 74 deletions
  1. 1 1
      Makefile
  2. 1 6
      src/ranch_acceptors_sup.erl
  3. 14 6
      src/ranch_tcp.erl
  4. 128 47
      test/acceptor_SUITE.erl
  5. 2 0
      test/ranch_ct_hook.erl
  6. 24 3
      test/sendfile_SUITE.erl
  7. 66 11
      test/stampede_SUITE.erl

+ 1 - 1
Makefile

@@ -18,7 +18,7 @@ DOC_DEPS = asciideck
 
 TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper stampede
 dep_ct_helper = git https://github.com/ninenines/ct_helper master
-dep_stampede = git https://github.com/juhlig/stampede 0.5.0
+dep_stampede = git https://github.com/juhlig/stampede 0.6.0
 
 # Concuerror tests.
 

+ 1 - 6
src/ranch_acceptors_sup.erl

@@ -60,12 +60,7 @@ start_listen_sockets(Ref, NumListenSockets, Transport, TransOpts0, Logger) when
 			[];
 		{_, Port} ->
 			SocketOpts = maps:get(socket_opts, TransOpts0, []),
-			SocketOpts1 = case lists:keyfind(port, 1, SocketOpts) of
-				{port, Port} ->
-					SocketOpts;
-				_ ->
-					[{port, Port}|lists:keydelete(port, 1, SocketOpts)]
-			end,
+			SocketOpts1 = lists:keystore(port, 1, SocketOpts, {port, Port}),
 			TransOpts1 = TransOpts0#{socket_opts => SocketOpts1},
 			[{N, start_listen_socket(Ref, Transport, TransOpts1, Logger)}
 				|| N <- lists:seq(2, NumListenSockets)]

+ 14 - 6
src/ranch_tcp.erl

@@ -90,16 +90,24 @@ messages() -> {tcp, tcp_closed, tcp_error, tcp_passive}.
 listen(TransOpts) ->
 	ok = cleanup(TransOpts),
 	Logger = maps:get(logger, TransOpts, logger),
-	SocketOpts0 = maps:get(socket_opts, TransOpts, []),
+	SocketOpts = maps:get(socket_opts, TransOpts, []),
+	%% We set the port to 0 because it is given in the Opts directly.
+	%% The port in the options takes precedence over the one in the
+	%% first argument.
+	gen_tcp:listen(0, prepare_socket_opts(SocketOpts, Logger)).
+
+prepare_socket_opts([Backend = {inet_backend, _}|SocketOpts], Logger) ->
+	%% In OTP/23, the inet_backend option may be used to activate the
+	%% experimental socket backend for inet/gen_tcp. If present, it must
+	%% be the first option in the list.
+	[Backend|prepare_socket_opts(SocketOpts, Logger)];
+prepare_socket_opts(SocketOpts0, Logger) ->
 	SocketOpts1 = ranch:set_option_default(SocketOpts0, backlog, 1024),
 	SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true),
 	SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000),
 	SocketOpts4 = ranch:set_option_default(SocketOpts3, send_timeout_close, true),
-	%% We set the port to 0 because it is given in the Opts directly.
-	%% The port in the options takes precedence over the one in the
-	%% first argument.
-	gen_tcp:listen(0, ranch:filter_options(SocketOpts4, disallowed_listen_options(),
-		[binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)).
+	ranch:filter_options(SocketOpts4, disallowed_listen_options(),
+		[binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger).
 
 %% 'binary' and 'list' are disallowed but they are handled
 %% specifically as they do not have 2-tuple equivalents.

+ 128 - 47
test/acceptor_SUITE.erl

@@ -21,13 +21,14 @@
 %% @todo Remove when specs in ssl are updated to accept local addresses.
 -dialyzer({nowarn_function, do_ssl_local_echo/0}).
 
+-import(ct_helper, [config/2]).
 -import(ct_helper, [doc/1]).
 -import(ct_helper, [name/0]).
 
 %% ct.
 
 all() ->
-	[{group, tcp}, {group, ssl}, {group, misc}, {group, supervisor}].
+	[{group, tcp}, {group, tcp_socket}, {group, ssl}, {group, misc}, {group, supervisor}].
 
 groups() ->
 	[{tcp, [
@@ -51,6 +52,30 @@ groups() ->
 		tcp_many_listen_sockets_no_reuseport,
 		tcp_error_eaddrinuse,
 		tcp_error_eacces
+	]}, {tcp_socket, [
+		tcp_active_echo,
+		tcp_active_n_echo,
+		tcp_echo,
+		tcp_graceful,
+		tcp_inherit_options,
+		tcp_max_connections,
+		tcp_max_connections_and_beyond,
+		tcp_max_connections_infinity,
+		tcp_remove_connections,
+		tcp_remove_connections_acceptor_wakeup,
+		tcp_set_max_connections,
+		tcp_set_max_connections_clean,
+		tcp_getopts_capability,
+		tcp_getstat_capability,
+		tcp_upgrade,
+		%% @TODO: Enable when https://github.com/erlang/otp/issues/5122
+		%%        is in an official release, probably 24.1.
+		% tcp_10_acceptors_10_listen_sockets,
+		% tcp_many_listen_sockets_no_reuseport,
+		tcp_error_eaddrinuse
+		%% @TODO: Not working in OTP/24.0 but fixed in current master.
+		%%        Enable when fixed in an official release, probably 24.1.
+		% tcp_error_eacces
 	]}, {ssl, [
 		ssl_accept_error,
 		ssl_active_echo,
@@ -102,6 +127,37 @@ groups() ->
 		supervisor_unexpected_message
 	]}].
 
+init_per_group(tcp_socket, Config) ->
+	%% The socket backend for inet/gen_tcp was introduced as an experimental
+	%% feature in OTP/23.0, and bugs https://bugs.erlang.org/browse/ERL-1284,
+	%% 1287 and 1293 were solved in OTP/23.1. socket:use_registry/1 first
+	%% appears in this release.
+	%% Due to https://bugs.erlang.org/browse/ERL-1401, the socket backend
+	%% is not working on Windows.
+	case
+		os:type() =/= {win32, nt} andalso
+		code:ensure_loaded(socket) =:= {module, socket} andalso
+		erlang:function_exported(socket, use_registry, 1)
+	of
+		true ->
+			[{socket_opts, [{inet_backend, socket}]}|Config];
+		false ->
+			{skip, "No socket backend support"}
+	end;
+init_per_group(_, Config) ->
+	case
+		code:ensure_loaded(socket) =:= {module, socket} andalso
+		erlang:function_exported(socket, use_registry, 1)
+	of
+		true ->
+			[{socket_opts, [{inet_backend, inet}]}|Config];
+		false ->
+			[{socket_opts, []}|Config]
+	end.
+
+end_per_group(_, _) ->
+	ok.
+
 %% misc.
 
 misc_bad_transport(_) ->
@@ -961,53 +1017,62 @@ do_ssl_unsupported_tlsv13_options() ->
 
 %% tcp.
 
-tcp_10_acceptors_10_listen_sockets(_) ->
+tcp_10_acceptors_10_listen_sockets(Config) ->
 	case do_os_supports_reuseport() of
 		true ->
-			ok = do_tcp_10_acceptors_10_listen_sockets();
+			ok = do_tcp_10_acceptors_10_listen_sockets(Config);
 		false ->
 			{skip, "No SO_REUSEPORT support."}
 	end.
 
-do_tcp_10_acceptors_10_listen_sockets() ->
+do_tcp_10_acceptors_10_listen_sockets(Config) ->
 	doc("Ensure that we can use 10 listen sockets across 10 acceptors with TCP."),
 	Name = name(),
-	{ok, ListenerSupPid} = ranch:start_listener(Name,
+	SockOpts = config(socket_opts, Config),
+	Self = self(),
+	Tag = make_ref(),
+	{ok, _} = ranch:start_listener(Name,
 		ranch_tcp, #{
 			num_acceptors => 10,
 			num_listen_sockets => 10,
-			socket_opts => [{raw, 1, 15, <<1:32/native>>}]},
+			socket_opts => SockOpts ++ [{raw, 1, 15, <<1:32/native>>}],
+			post_listen_callback => fun (LSocket) -> Self ! {Tag, LSocket}, ok end},
 		echo_protocol, []),
-	10 = length(do_get_listener_sockets(ListenerSupPid)),
+	LSockets = [receive {Tag, LSocket} -> LSocket after 1000 -> error(timeout) end
+		|| _ <- lists:seq(1, 10)],
+	10 = length(LSockets),
+	10 = length(lists:usort(LSockets)),
 	ok = ranch:stop_listener(Name),
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_many_listen_sockets_no_reuseport(_) ->
+tcp_many_listen_sockets_no_reuseport(Config) ->
 	case do_os_supports_reuseport() of
 		true ->
-			ok = do_tcp_many_listen_sockets_no_reuseport();
+			ok = do_tcp_many_listen_sockets_no_reuseport(Config);
 		false ->
 			{skip, "No SO_REUSEPORT support."}
 	end.
 
-do_tcp_many_listen_sockets_no_reuseport() ->
+do_tcp_many_listen_sockets_no_reuseport(Config) ->
 	doc("Confirm that ranch:start_listener/5 fails when SO_REUSEPORT is not available with TCP."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{error, eaddrinuse} = ranch:start_listener(Name,
 		ranch_tcp, #{
 			num_acceptors => 10,
 			num_listen_sockets => 10,
-			socket_opts => [{raw, 1, 15, <<0:32/native>>}]},
+			socket_opts => SockOpts ++ [{raw, 1, 15, <<0:32/native>>}]},
 		echo_protocol, []),
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_active_echo(_) ->
+tcp_active_echo(Config) ->
 	doc("Ensure that active mode works with TCP transport."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		active_echo_protocol, []),
 	Port = ranch:get_port(Name),
 	{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
@@ -1019,11 +1084,12 @@ tcp_active_echo(_) ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_active_n_echo(_) ->
+tcp_active_n_echo(Config) ->
 	doc("Ensure that active N mode works with TCP transport."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		batch_echo_protocol, [{batch_size, 3}]),
 	Port = ranch:get_port(Name),
 	{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
@@ -1040,11 +1106,12 @@ tcp_active_n_echo(_) ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_echo(_) ->
+tcp_echo(Config) ->
 	doc("Ensure that passive mode works with TCP transport."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		echo_protocol, []),
 	Port = ranch:get_port(Name),
 	{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
@@ -1087,11 +1154,12 @@ do_tcp_local_echo() ->
 		file:delete(SockFile)
 	end.
 
-tcp_graceful(_) ->
+tcp_graceful(Config) ->
 	doc("Ensure suspending and resuming of listeners does not kill active connections."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		echo_protocol, []),
 	Port = ranch:get_port(Name),
 	%% Make sure connections with a fresh listener work.
@@ -1123,24 +1191,26 @@ tcp_graceful(_) ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_inherit_options(_) ->
+tcp_inherit_options(Config) ->
 	doc("Ensure TCP options are inherited in the protocol."),
 	Name = name(),
-	Opts = [{nodelay, false}, {send_timeout_close, false}],
+	Opts0 = config(socket_opts, Config),
+	Opts1 = [{nodelay, false}, {send_timeout_close, false}],
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, Opts,
-		check_tcp_options, [{pid, self()} | Opts]),
+		ranch_tcp, #{socket_opts => Opts0 ++ Opts1},
+		check_tcp_options, [{pid, self()} | Opts1]),
 	Port = ranch:get_port(Name),
 	{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, true}, {packet, raw}]),
 	receive checked -> ok after 1000 -> error(timeout) end,
 	ok = gen_tcp:close(Socket),
 	ok = ranch:stop_listener(Name).
 
-tcp_max_connections(_) ->
+tcp_max_connections(Config) ->
 	doc("Ensure the max_connections option actually limits connections."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{max_connections => 10, num_acceptors => 1},
+		ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts},
 		notify_and_wait_protocol, #{pid => self()}),
 	Port = ranch:get_port(Name),
 	ok = connect_loop(Port, 11, 150),
@@ -1151,11 +1221,12 @@ tcp_max_connections(_) ->
 	ok = terminate_loop(stop, Pids2),
 	ok = ranch:stop_listener(Name).
 
-tcp_max_connections_and_beyond(_) ->
+tcp_max_connections_and_beyond(Config) ->
 	doc("Ensure the max_connections option works when connections are removed from the count."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{max_connections => 10, num_acceptors => 1},
+		ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts},
 		remove_conn_and_wait_protocol, [{remove, true, 2500}]),
 	Port = ranch:get_port(Name),
 	ok = connect_loop(Port, 10, 0),
@@ -1178,11 +1249,12 @@ tcp_max_connections_and_beyond(_) ->
 	{_, 20} = lists:keyfind(workers, 1, Counts2),
 	ok = ranch:stop_listener(Name).
 
-tcp_max_connections_infinity(_) ->
+tcp_max_connections_infinity(Config) ->
 	doc("Set the max_connections option from 10 to infinity and back to 10."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{max_connections => 10, num_acceptors => 1},
+		ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts},
 		notify_and_wait_protocol, #{pid => self()}),
 	Port = ranch:get_port(Name),
 	ok = connect_loop(Port, 20, 0),
@@ -1200,11 +1272,12 @@ tcp_max_connections_infinity(_) ->
 	ok = terminate_loop(stop, Pids1 ++ Pids2),
 	ok = ranch:stop_listener(Name).
 
-tcp_remove_connections(_) ->
+tcp_remove_connections(Config) ->
 	doc("Ensure that removed connections are only removed once."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		remove_conn_and_wait_protocol, [{remove, true, 0}]),
 	Port = ranch:get_port(Name),
 	ok = connect_loop(Port, 10, 0),
@@ -1212,11 +1285,12 @@ tcp_remove_connections(_) ->
 	0 = ranch_server:count_connections(Name),
 	ok = ranch:stop_listener(Name).
 
-tcp_remove_connections_acceptor_wakeup(_) ->
+tcp_remove_connections_acceptor_wakeup(Config) ->
 	doc("Ensure that removed connections wake up acceptors."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{max_connections => 1, num_acceptors => 1},
+		ranch_tcp, #{max_connections => 1, num_acceptors => 1, socket_opts => SockOpts},
 		remove_conn_and_wait_protocol, [{remove, true, infinity}]),
 	Port = ranch:get_port(Name),
 	ConnectOptions = [binary, {active, false}],
@@ -1230,11 +1304,12 @@ tcp_remove_connections_acceptor_wakeup(_) ->
 	ok = gen_tcp:send(Socket2, <<"bye">>),
 	ok = ranch:stop_listener(Name).
 
-tcp_set_max_connections(_) ->
+tcp_set_max_connections(Config) ->
 	doc("Ensure that changing the max_connections option to a larger value allows for more connections."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{max_connections => 10, num_acceptors => 1},
+		ranch_tcp, #{max_connections => 10, num_acceptors => 1, socket_opts => SockOpts},
 		notify_and_wait_protocol, #{pid => self()}),
 	Port = ranch:get_port(Name),
 	ok = connect_loop(Port, 20, 0),
@@ -1247,11 +1322,12 @@ tcp_set_max_connections(_) ->
 	ok = terminate_loop(stop, Pids1 ++ Pids2),
 	ok = ranch:stop_listener(Name).
 
-tcp_set_max_connections_clean(_) ->
+tcp_set_max_connections_clean(Config) ->
 	doc("Ensure that setting max_connections does not crash any process."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, ListSupPid} = ranch:start_listener(Name,
-		ranch_tcp, #{max_connections => 4},
+		ranch_tcp, #{max_connections => 4, socket_opts => SockOpts},
 		notify_and_wait_protocol, #{pid => self()}),
 	Children = supervisor:which_children(ListSupPid),
 	{_, AccSupPid, _, _} = lists:keyfind(ranch_acceptors_sup, 1, Children),
@@ -1273,11 +1349,12 @@ tcp_set_max_connections_clean(_) ->
 	ok = clean_traces(),
 	ok = ranch:stop_listener(Name).
 
-tcp_getopts_capability(_) ->
+tcp_getopts_capability(Config) ->
 	doc("Ensure getopts/2 capability."),
 	Name=name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _}=ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		transport_capabilities_protocol, []),
 	Port=ranch:get_port(Name),
 	{ok, Socket}=gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
@@ -1288,11 +1365,12 @@ tcp_getopts_capability(_) ->
 	{'EXIT', _}=begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_getstat_capability(_) ->
+tcp_getstat_capability(Config) ->
 	doc("Ensure getstat/1,2 capability."),
 	Name=name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _}=ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		transport_capabilities_protocol, []),
 	Port=ranch:get_port(Name),
 	{ok, Socket}=gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
@@ -1305,11 +1383,12 @@ tcp_getstat_capability(_) ->
 	{'EXIT', _}=begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_upgrade(_) ->
+tcp_upgrade(Config) ->
 	doc("Ensure that protocol options can be updated."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		notify_and_wait_protocol, #{pid => self()}),
 	Port = ranch:get_port(Name),
 	ok = connect_loop(Port, 1, 0),
@@ -1320,11 +1399,12 @@ tcp_upgrade(_) ->
 	ok = terminate_loop(stop, Pids1 ++ Pids2),
 	ok = ranch:stop_listener(Name).
 
-tcp_error_eaddrinuse(_) ->
+tcp_error_eaddrinuse(Config) ->
 	doc("Ensure that failure due to an eaddrinuse returns a compact readable error."),
 	Name = name(),
+	SockOpts = config(socket_opts, Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		active_echo_protocol, []),
 	Port = ranch:get_port(Name),
 	{error, eaddrinuse} = ranch:start_listener({Name, fails},
@@ -1335,7 +1415,7 @@ tcp_error_eaddrinuse(_) ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
-tcp_error_eacces(_) ->
+tcp_error_eacces(Config) ->
 	case os:type() of
 		{win32, nt} ->
 			{skip, "No privileged ports."};
@@ -1344,8 +1424,9 @@ tcp_error_eacces(_) ->
 		_ ->
 			doc("Ensure that failure due to an eacces returns a compact readable error."),
 			Name = name(),
+			SockOpts = config(socket_opts, Config),
 			{error, eacces} = ranch:start_listener(Name,
-				ranch_tcp, [{port, 283}],
+				ranch_tcp, #{socket_opts => SockOpts ++ [{port, 283}]},
 				active_echo_protocol, []),
 			ok
 	end.

+ 2 - 0
test/ranch_ct_hook.erl

@@ -32,6 +32,8 @@ init(_, _) ->
 			ok;
 		{{win32, nt}, {ok, "9."++_}} ->
 			application:set_env(ssl, internal_active_n, 1);
+		{{win32, nt}, {ok, "10."++_}} ->
+			application:set_env(ssl, internal_active_n, 1);
 		_ ->
 			ok
 	end,

+ 24 - 3
test/sendfile_SUITE.erl

@@ -21,7 +21,7 @@
 -import(ct_helper, [doc/1]).
 
 all() ->
-	[{group, tcp}, {group, ssl}].
+	[{group, tcp}, {group, tcp_socket}, {group, ssl}].
 
 suite() ->
 	[{timetrap, {seconds, 60}}].
@@ -38,7 +38,11 @@ groups() ->
 		rawfile_range_medium,
 		rawfile_range_small
 	],
-	[{tcp, [parallel], Tests}, {ssl, [parallel], Tests ++ [ssl_chunk_size]}].
+	[
+		{tcp, [parallel], Tests},
+		{tcp_socket, [parallel], Tests},
+		{ssl, [parallel], Tests ++ [ssl_chunk_size]}
+	].
 
 init_per_suite(Config) ->
 	Filename = filename:join(config(priv_dir, Config), "sendfile"),
@@ -55,7 +59,24 @@ init_per_group(ssl, Config) ->
 	SslOpts = ct_helper:get_certs_from_ets(),
 	[{transport, ranch_ssl}, {transport_opts, SslOpts} | Config];
 init_per_group(tcp, Config) ->
-	[{transport, ranch_tcp}, {transport_opts, []} | Config].
+	[{transport, ranch_tcp}, {transport_opts, []} | Config];
+init_per_group(tcp_socket, Config) ->
+        %% The socket backend for inet/gen_tcp was introduced as an experimental
+        %% feature in OTP/23.0, and bugs https://bugs.erlang.org/browse/ERL-1284,
+        %% 1287 and 1293 were solved in OTP/23.1. socket:use_registry/1 first
+        %% appears in this release.
+        %% Due to https://bugs.erlang.org/browse/ERL-1401, the socket backend
+        %% is not working on Windows.
+	case
+		os:type() =/= {win32, nt} andalso
+		code:ensure_loaded(socket) =:= {module, socket} andalso
+		erlang:function_exported(socket, use_registry, 1)
+	of
+		true ->
+			[{transport, ranch_tcp}, {transport_opts, [{inet_backend, socket}]} | Config];
+		false ->
+			{skip, "No socket backend support"}
+	end.
 
 end_per_group(_, _) ->
 	ok.

+ 66 - 11
test/stampede_SUITE.erl

@@ -22,7 +22,57 @@
 %% ct.
 
 all() ->
-	ct_helper:all(?MODULE).
+	[{group, tcp}, {group, tcp_socket}, {group, ssl}].
+
+groups() ->
+	[
+		{
+			tcp,
+			[],
+			[
+				stampede_tcp,
+				stampede_embedded
+			]
+		},
+		{
+			tcp_socket,
+			[],
+			[
+				stampede_tcp,
+				stampede_embedded
+			]
+		},
+		{
+			ssl,
+			[],
+			[
+				stampede_ssl
+			]
+		}
+	].
+
+init_per_group(tcp_socket, Config) ->
+	%% The socket backend for inet/gen_tcp was introduced as an experimental
+	%% feature in OTP/23.0, and bugs https://bugs.erlang.org/browse/ERL-1284,
+	%% 1287 and 1293 were solved in OTP/23.1. socket:use_registry/1 first
+	%% appears in this release.
+        %% Due to https://bugs.erlang.org/browse/ERL-1401, the socket backend
+        %% is not working on Windows.
+	case
+		os:type() =/= {win32, nt} andalso
+		code:ensure_loaded(socket) =:= {module, socket} andalso
+		erlang:function_exported(socket, use_registry, 1)
+	of
+		true ->
+			[{socket_opts, [{inet_backend, socket}]}|Config];
+		false ->
+			{skip, "No socket backend support"}
+	end;
+init_per_group(_, Config) ->
+	Config.
+
+end_per_group(_, _) ->
+	ok.
 
 init_per_suite(Config) ->
 	{ok, _} = application:ensure_all_started(ranch),
@@ -40,21 +90,22 @@ end_per_suite(_) ->
 
 %% Tests.
 
-stampede_tcp(_) ->
+stampede_tcp(Config) ->
 	doc("Start a TCP listener, establish a hundred connections, "
 		"run stampede, confirm we can still connect."),
 	%% Start a TCP listener.
 	Name = name(),
+	SockOpts = do_get_sockopts(Config),
 	{ok, _} = ranch:start_listener(Name,
-		ranch_tcp, #{},
+		ranch_tcp, #{socket_opts => SockOpts},
 		echo_protocol, []),
-	%% Establish a hundred connections.
-	ok = do_connect(100, ranch_tcp, ranch:get_port(Name), 1000),
 	%% Set restart frequency of ranch_sup.
 	do_set_sup_frequencies([ranch_sup], 999999, 1),
 	%% Run stampede.
 	{ok, _} = stampede:start_herd(ranch_stampede, {application, ranch},
 		#{interval => {100, 100}, before_kill => fun do_log/1}),
+	%% Establish a hundred connections.
+	ok = do_connect(100, ranch_tcp, ranch:get_port(Name), 1000),
 	ok = stampede:activate(ranch_stampede),
 	timer:sleep(10000),
 	ok = stampede:stop_herd(ranch_stampede),
@@ -71,8 +122,6 @@ stampede_ssl(_) ->
 	{ok, _} = ranch:start_listener(Name,
 		ranch_ssl, ct_helper:get_certs_from_ets(),
 		echo_protocol, []),
-	%% Establish a hundred connections.
-	ok = do_connect(100, ranch_ssl, ranch:get_port(Name), 1000),
 	%% Set restart frequencies of ranch_sup and ssl_sup.
 	do_set_sup_frequencies([ranch_sup, ssl_sup], 999999, 1),
 	%% Run stampede.
@@ -80,6 +129,8 @@ stampede_ssl(_) ->
 		#{interval => {100, 100}, before_kill => fun do_log/1}),
 	{ok, _} = stampede:start_herd(ssl_stampede, {application, ssl},
 		#{interval => {100, 100}, before_kill => fun do_log/1}),
+	%% Establish a hundred connections.
+	ok = do_connect(100, ranch_ssl, ranch:get_port(Name), 1000),
 	ok = stampede:activate(ranch_stampede),
 	ok = stampede:activate(ssl_stampede),
 	timer:sleep(10000),
@@ -90,16 +141,15 @@ stampede_ssl(_) ->
 	ok = do_connect(1, ranch_ssl, ranch:get_port(Name), 1000),
 	ok = ranch:stop_listener(Name).
 
-stampede_embedded(_) ->
+stampede_embedded(Config) ->
 	doc("Start an embedded TCP listener, establish a hundred connections, "
 		"run stampede, confirm we can still connect."),
 	%% Start embedded listener.
 	Name = name(),
+	SockOpts = do_get_sockopts(Config),
 	{ok, SupPid} = embedded_sup:start_link(),
 	{ok, _} = embedded_sup:start_listener(SupPid, Name,
-		ranch_tcp, #{}, echo_protocol, []),
-	%% Establish a hundred connections.
-	ok = do_connect(100, ranch_tcp, ranch:get_port(Name), 1000),
+		ranch_tcp, #{socket_opts => SockOpts}, echo_protocol, []),
 	%% Set restart frequency of ranch_sup and embedded_sup.
 	do_set_sup_frequencies([ranch_sup, SupPid], 999999, 1),
 	%% Run stampede.
@@ -107,6 +157,8 @@ stampede_embedded(_) ->
 		#{interval => {100, 100}, before_kill => fun do_log/1}),
 	{ok, _} = stampede:start_herd(embedded_stampede, {supervisor, SupPid},
 		#{interval => {100, 100}, before_kill => fun do_log/1}),
+	%% Establish a hundred connections.
+	ok = do_connect(100, ranch_tcp, ranch:get_port(Name), 1000),
 	ok = stampede:activate(ranch_stampede),
 	ok = stampede:activate(embedded_stampede),
 	timer:sleep(10000),
@@ -136,3 +188,6 @@ do_log(Pid) when is_pid(Pid) ->
 do_log(Port) when is_port(Port) ->
 	ct:log(info, "~p: ~p~n", [Port, erlang:port_info(Port)]),
 	true.
+
+do_get_sockopts(Config) ->
+	proplists:get_value(socket_opts, Config, []).