From f74b69c3edd6e8c8891a3a8e18d14c8ae93bf5c4 Mon Sep 17 00:00:00 2001 From: "Robert J. Macomber" Date: Mon, 8 Feb 2021 16:05:05 -0800 Subject: 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. --- test/http_SUITE.erl | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) (limited to 'test/http_SUITE.erl') diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index ccac04f..8b8473f 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -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 -- cgit v1.2.3