0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Optionally reset the idle timeout when sending data

A new option reset_idle_timeout_on_send has been added.
When set to 'true', the idle timeout is reset not only
when data is received, but also when data is sent.

This allows sending large responses without having to
worry about timeouts triggering.

The default is currently unchanged but might change in
a future release.

LH: Greatly reworked the implementation so that the
    timeout gets reset on almost all socket writes.
	This essentially completely supersets the original
	work. Tests are mostly the same although I
	refactored a bit to avoid test code duplication.

This commit also changes HTTP/2 behavior a little when
data is received: Cowboy will not attempt to update the
window before running stream handler commands to avoid
sending WINDOW_UPDATE frames twice. Now it has some
small heuristic to ensure they can only be sent once
at most.
This commit is contained in:
Robert J. Macomber 2021-02-08 16:05:05 -08:00 committed by Loïc Hoguin
parent 7400b04b02
commit f74b69c3ed
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
5 changed files with 225 additions and 38 deletions

View file

@ -45,7 +45,8 @@ init_dispatch(_) ->
{"/", hello_h, []},
{"/echo/:key", echo_h, []},
{"/resp/:key[/:arg]", resp_h, []},
{"/set_options/:key", set_options_h, []}
{"/set_options/:key", set_options_h, []},
{"/streamed_result/:n/:interval", streamed_result_h, []}
]}]).
chunked_false(Config) ->
@ -252,6 +253,82 @@ idle_timeout_infinity(Config) ->
cowboy:stop_listener(?FUNCTION_NAME)
end.
idle_timeout_on_send(Config) ->
doc("Ensure the idle timeout is not reset when sending (by default)."),
do_idle_timeout_on_send(Config, http).
%% Also used by http2_SUITE.
do_idle_timeout_on_send(Config, Protocol) ->
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
env => #{dispatch => init_dispatch(Config)},
idle_timeout => 1000
}),
Port = ranch:get_port(?FUNCTION_NAME),
try
ConnPid = gun_open([{type, tcp}, {protocol, Protocol}, {port, Port}|Config]),
{ok, Protocol} = gun:await_up(ConnPid),
#{socket := Socket} = gun:info(ConnPid),
Pid = get_remote_pid_tcp(Socket),
StreamRef = gun:get(ConnPid, "/streamed_result/10/250"),
Ref = erlang:monitor(process, Pid),
receive
{gun_response, ConnPid, StreamRef, nofin, _Status, _Headers} ->
do_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, false)
after 2000 ->
error(timeout)
end
after
cowboy:stop_listener(?FUNCTION_NAME)
end.
idle_timeout_reset_on_send(Config) ->
doc("Ensure the reset_idle_timeout_on_send results in the "
"idle timeout resetting when sending ."),
do_idle_timeout_reset_on_send(Config, http).
%% Also used by http2_SUITE.
do_idle_timeout_reset_on_send(Config, Protocol) ->
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
env => #{dispatch => init_dispatch(Config)},
idle_timeout => 1000,
reset_idle_timeout_on_send => true
}),
Port = ranch:get_port(?FUNCTION_NAME),
try
ConnPid = gun_open([{type, tcp}, {protocol, Protocol}, {port, Port}|Config]),
{ok, Protocol} = gun:await_up(ConnPid),
#{socket := Socket} = gun:info(ConnPid),
Pid = get_remote_pid_tcp(Socket),
StreamRef = gun:get(ConnPid, "/streamed_result/10/250"),
Ref = erlang:monitor(process, Pid),
receive
{gun_response, ConnPid, StreamRef, nofin, _Status, _Headers} ->
do_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, true)
after 2000 ->
error(timeout)
end
after
cowboy:stop_listener(?FUNCTION_NAME)
end.
do_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, ExpectCompletion) ->
receive
{gun_data, ConnPid, StreamRef, nofin, _Data} ->
do_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, ExpectCompletion);
{gun_data, ConnPid, StreamRef, fin, _Data} when ExpectCompletion ->
gun:close(ConnPid);
{gun_data, ConnPid, StreamRef, fin, _Data} ->
gun:close(ConnPid),
error(completed);
{'DOWN', Ref, process, Pid, _} when ExpectCompletion ->
gun:close(ConnPid),
error(exited);
{'DOWN', Ref, process, Pid, _} ->
ok
after 2000 ->
error(timeout)
end.
persistent_term_router(Config) ->
doc("The router can retrieve the routes from persistent_term storage."),
case erlang:function_exported(persistent_term, get, 1) of