Browse Source

Add test for send_timeout_close

LH: I reworked the test a little and added the same test
for HTTP/2 so that both HTTP/1.1 and HTTP/2 get the issue
fixed.
Sergei Shuvatov 2 years ago
parent
commit
3f5f326b73
3 changed files with 149 additions and 1 deletions
  1. 24 0
      test/handlers/loop_handler_endless_h.erl
  2. 65 1
      test/http2_SUITE.erl
  3. 60 0
      test/http_SUITE.erl

+ 24 - 0
test/handlers/loop_handler_endless_h.erl

@@ -0,0 +1,24 @@
+%% This module implements a loop handler that streams endless data.
+
+-module(loop_handler_endless_h).
+
+-export([init/2]).
+-export([info/3]).
+
+init(Req0, #{delay := Delay} = Opts) ->
+	case cowboy_req:header(<<"x-test-pid">>, Req0) of
+		BinPid when is_binary(BinPid) ->
+			Pid = list_to_pid(binary_to_list(BinPid)),
+			Pid ! {Pid, self(), init},
+			ok;
+		_ ->
+			ok
+	end,
+	erlang:send_after(Delay, self(), timeout),
+	Req = cowboy_req:stream_reply(200, Req0),
+	{cowboy_loop, Req, Opts}.
+
+info(timeout, Req, State) ->
+	cowboy_req:stream_body(<<0:1000/unit:8>>, nofin, Req),
+	erlang:send_after(10, self(), timeout),
+	{ok, Req, State}.

+ 65 - 1
test/http2_SUITE.erl

@@ -37,7 +37,8 @@ do_handshake(Config) ->
 	do_handshake(#{}, Config).
 	do_handshake(#{}, Config).
 
 
 do_handshake(Settings, Config) ->
 do_handshake(Settings, Config) ->
-	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
+	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config),
+		[binary, {active, false}|proplists:get_value(tcp_opts, Config, [])]),
 	%% Send a valid preface.
 	%% Send a valid preface.
 	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(Settings)]),
 	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(Settings)]),
 	%% Receive the server preface.
 	%% Receive the server preface.
@@ -416,3 +417,66 @@ graceful_shutdown_listener_timeout(Config) ->
 	%% Check that the slow request is aborted.
 	%% Check that the slow request is aborted.
 	{error, {stream_error, closed}} = gun:await(ConnPid, Ref),
 	{error, {stream_error, closed}} = gun:await(ConnPid, Ref),
 	gun:close(ConnPid).
 	gun:close(ConnPid).
+
+send_timeout_close(Config) ->
+	doc("Check that connections are closed on send timeout."),
+	TransOpts = #{
+		port => 0,
+		socket_opts => [
+			{send_timeout, 100},
+			{send_timeout_close, true},
+			{sndbuf, 10}
+		]
+	},
+	Dispatch = cowboy_router:compile([{"localhost", [
+		{"/endless", loop_handler_endless_h, #{delay => 100}}
+	]}]),
+	ProtoOpts = #{
+		env => #{dispatch => Dispatch},
+		idle_timeout => infinity
+	},
+	{ok, _} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),
+	Port = ranch:get_port(?FUNCTION_NAME),
+	try
+		%% Connect a client that sends a request and waits indefinitely.
+		{ok, ClientSocket} = do_handshake([{port, Port},
+			{tcp_opts, [{recbuf, 10}, {buffer, 10}, {active, false}]}|Config]),
+		{HeadersBlock, _} = cow_hpack:encode([
+			{<<":method">>, <<"GET">>},
+			{<<":scheme">>, <<"http">>},
+			{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+			{<<":path">>, <<"/endless">>},
+			{<<"x-test-pid">>, pid_to_list(self())}
+		]),
+		ok = gen_tcp:send(ClientSocket, cow_http2:headers(1, fin, HeadersBlock)),
+		%% Wait for the handler to start then get its pid,
+		%% the remote connection's pid and socket.
+		StreamPid = receive
+			{Self, StreamPid0, init} when Self =:= self() ->
+				StreamPid0
+		after 1000 ->
+			error(timeout)
+		end,
+		ServerPid = ct_helper:get_remote_pid_tcp(ClientSocket),
+		{links, ServerLinks} = process_info(ServerPid, links),
+		[ServerSocket] = [PidOrPort || PidOrPort <- ServerLinks, is_port(PidOrPort)],
+		%% Poll the socket repeatedly until it is closed by the server.
+		WaitClosedFun =
+			fun F(T, Status) when T =< 0 ->
+					error({status, Status});
+				F(T, _) ->
+					case prim_inet:getstatus(ServerSocket) of
+						{error, _} ->
+							ok;
+						{ok, Status} ->
+							Snooze = 100,
+							timer:sleep(Snooze),
+							F(T - Snooze, Status)
+					end
+			end,
+		ok = WaitClosedFun(2000, undefined),
+		false = erlang:is_process_alive(StreamPid),
+		false = erlang:is_process_alive(ServerPid)
+	after
+		cowboy:stop_listener(?FUNCTION_NAME)
+	end.

+ 60 - 0
test/http_SUITE.erl

@@ -514,3 +514,63 @@ graceful_shutdown_listener(Config) ->
 	%% Check that the 2nd (very slow) request is not handled.
 	%% Check that the 2nd (very slow) request is not handled.
 	{error, {stream_error, closed}} = gun:await(ConnPid2, Ref2),
 	{error, {stream_error, closed}} = gun:await(ConnPid2, Ref2),
 	gun:close(ConnPid2).
 	gun:close(ConnPid2).
+
+send_timeout_close(_Config) ->
+	doc("Check that connections are closed on send timeout."),
+	TransOpts = #{
+		port => 0,
+		socket_opts => [
+			{send_timeout, 100},
+			{send_timeout_close, true},
+			{sndbuf, 10}
+		]
+	},
+	Dispatch = cowboy_router:compile([{"localhost", [
+		{"/endless", loop_handler_endless_h, #{delay => 100}}
+	]}]),
+	ProtoOpts = #{
+		env => #{dispatch => Dispatch},
+		idle_timeout => infinity
+	},
+	{ok, _} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),
+	Port = ranch:get_port(?FUNCTION_NAME),
+	try
+		%% Connect a client that sends a request and waits indefinitely.
+		{ok, ClientSocket} = gen_tcp:connect("localhost", Port,
+			[{recbuf, 10}, {buffer, 10}, {active, false}, {packet, 0}]),
+		ok = gen_tcp:send(ClientSocket, [
+			"GET /endless HTTP/1.1\r\n",
+			"Host: localhost:", integer_to_list(Port), "\r\n",
+			"x-test-pid: ", pid_to_list(self()), "\r\n\r\n"
+		]),
+		%% Wait for the handler to start then get its pid,
+		%% the remote connection's pid and socket.
+		StreamPid = receive
+			{Self, StreamPid0, init} when Self =:= self() ->
+				StreamPid0
+		after 1000 ->
+			error(timeout)
+		end,
+		ServerPid = ct_helper:get_remote_pid_tcp(ClientSocket),
+		{links, ServerLinks} = process_info(ServerPid, links),
+		[ServerSocket] = [PidOrPort || PidOrPort <- ServerLinks, is_port(PidOrPort)],
+		%% Poll the socket repeatedly until it is closed by the server.
+		WaitClosedFun =
+			fun F(T, Status) when T =< 0 ->
+					error({status, Status});
+				F(T, _) ->
+					case prim_inet:getstatus(ServerSocket) of
+						{error, _} ->
+							ok;
+						{ok, Status} ->
+							Snooze = 100,
+							timer:sleep(Snooze),
+							F(T - Snooze, Status)
+					end
+			end,
+		ok = WaitClosedFun(2000, undefined),
+		false = erlang:is_process_alive(StreamPid),
+		false = erlang:is_process_alive(ServerPid)
+	after
+		cowboy:stop_listener(?FUNCTION_NAME)
+	end.