aboutsummaryrefslogtreecommitdiffstats
path: root/test/http_SUITE.erl
diff options
context:
space:
mode:
authorViktor Söderqvist <[email protected]>2020-10-08 17:53:25 +0200
committerLoïc Hoguin <[email protected]>2020-11-27 15:38:21 +0100
commit059d58d39fb12765fa6e42c8d95c861ac85c23e2 (patch)
tree00d297cba48eb63af7bf38f20e7d5207d4139834 /test/http_SUITE.erl
parentfa9c8ad832f72f44b70924c1aa3a2ab4fd04c8da (diff)
downloadcowboy-059d58d39fb12765fa6e42c8d95c861ac85c23e2.tar.gz
cowboy-059d58d39fb12765fa6e42c8d95c861ac85c23e2.tar.bz2
cowboy-059d58d39fb12765fa6e42c8d95c861ac85c23e2.zip
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master. Graceful shutdown for HTTP/2: 1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a timer is started (goaway_initial_timeout, default 1000ms), to wait for any in-flight requests sent by the client, and the status is set to 'closing_initiated'. If the client responds with GOAWAY and closes the connection, we're done. 2. A second GOAWAY frame is sent with the actual last stream id and the status is set to 'closing'. If no streams exist, the connection terminates. Otherwise a second timer (goaway_complete_timeout, default 3000ms) is started, to wait for the streams to complete. New streams are not accepted when status is 'closing'. 3. If all streams haven't completed after the second timeout, the connection is forcefully terminated. Graceful shutdown for HTTP/1.x: 1. If a request is currently being handled, it is waited for and the response is sent back to the client with the header "Connection: close". Then, the connection is closed. 2. If the current request handler is not finished within the time configured in transport option 'shutdown' (default 5000ms), the connection process is killed by its supervisor (ranch). Implemented for HTTP/1.x and HTTP/2 in the following scenarios: * When receiving exit signal 'shutdown' from the supervisor (e.g. when cowboy:stop_listener/3 is called). * When a connection process is requested to terminate using sys:terminate/2,3. LH: Edited tests a bit and added todos for useful tests to add.
Diffstat (limited to 'test/http_SUITE.erl')
-rw-r--r--test/http_SUITE.erl71
1 files changed, 71 insertions, 0 deletions
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 0b4edd9..d0c92e4 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -20,6 +20,7 @@
-import(ct_helper, [doc/1]).
-import(ct_helper, [get_remote_pid_tcp/1]).
-import(cowboy_test, [gun_open/1]).
+-import(cowboy_test, [gun_down/1]).
-import(cowboy_test, [raw_open/1]).
-import(cowboy_test, [raw_send/2]).
-import(cowboy_test, [raw_recv_head/1]).
@@ -443,3 +444,73 @@ switch_protocol_flush(Config) ->
after
cowboy:stop_listener(?FUNCTION_NAME)
end.
+
+graceful_shutdown_connection(Config) ->
+ doc("Check that the current request is handled before gracefully "
+ "shutting down a connection."),
+ Dispatch = cowboy_router:compile([{"localhost", [
+ {"/delay_hello", delay_hello_h,
+ #{delay => 500, notify_received => self()}},
+ {"/long_delay_hello", delay_hello_h,
+ #{delay => 10000, notify_received => self()}}
+ ]}]),
+ ProtoOpts = #{
+ env => #{dispatch => Dispatch}
+ },
+ {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),
+ Port = ranch:get_port(?FUNCTION_NAME),
+ try
+ ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),
+ {ok, http} = gun:await_up(ConnPid),
+ #{socket := Socket} = gun:info(ConnPid),
+ CowboyConnPid = get_remote_pid_tcp(Socket),
+ CowboyConnRef = erlang:monitor(process, CowboyConnPid),
+ Ref1 = gun:get(ConnPid, "/delay_hello"),
+ Ref2 = gun:get(ConnPid, "/delay_hello"),
+ receive {request_received, <<"/delay_hello">>} -> ok end,
+ receive {request_received, <<"/delay_hello">>} -> ok end,
+ ok = sys:terminate(CowboyConnPid, system_is_going_down),
+ {response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref1),
+ <<"close">> = proplists:get_value(<<"connection">>, RespHeaders),
+ {ok, RespBody} = gun:await_body(ConnPid, Ref1),
+ <<"Hello world!">> = iolist_to_binary(RespBody),
+ {error, {stream_error, _}} = gun:await(ConnPid, Ref2),
+ ok = gun_down(ConnPid),
+ receive
+ {'DOWN', CowboyConnRef, process, CowboyConnPid, _Reason} ->
+ ok
+ end
+ after
+ cowboy:stop_listener(?FUNCTION_NAME)
+ end.
+
+graceful_shutdown_listener(Config) ->
+ doc("Check that connections are shut down gracefully when stopping a listener."),
+ Dispatch = cowboy_router:compile([{"localhost", [
+ {"/delay_hello", delay_hello_h,
+ #{delay => 500, notify_received => self()}},
+ {"/long_delay_hello", delay_hello_h,
+ #{delay => 10000, notify_received => self()}}
+ ]}]),
+ ProtoOpts = #{
+ env => #{dispatch => Dispatch}
+ },
+ {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),
+ Port = ranch:get_port(?FUNCTION_NAME),
+ ConnPid1 = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),
+ Ref1 = gun:get(ConnPid1, "/delay_hello"),
+ ConnPid2 = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),
+ Ref2 = gun:get(ConnPid2, "/long_delay_hello"),
+ %% Shutdown listener while the handlers are working.
+ receive {request_received, <<"/delay_hello">>} -> ok end,
+ receive {request_received, <<"/long_delay_hello">>} -> ok end,
+ ok = cowboy:stop_listener(?FUNCTION_NAME),
+ %% Check that the 1st request is handled before shutting down.
+ {response, nofin, 200, RespHeaders} = gun:await(ConnPid1, Ref1),
+ <<"close">> = proplists:get_value(<<"connection">>, RespHeaders),
+ {ok, RespBody} = gun:await_body(ConnPid1, Ref1),
+ <<"Hello world!">> = iolist_to_binary(RespBody),
+ gun:close(ConnPid1),
+ %% Check that the 2nd (very slow) request is not handled.
+ {error, {stream_error, closed}} = gun:await(ConnPid2, Ref2),
+ gun:close(ConnPid2).