From 775091134dc49633595cfc5956612df0c5702c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 6 Apr 2020 14:50:35 +0200 Subject: Experiment with a linger_timeout for HTTP/2 This is mostly to ensure that the GOAWAY frame is properly received on Windows in some tests, but should be benefitial also in production in particular when clients are slower. --- src/cowboy_http2.erl | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index 5224737..8dc8c3b 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -35,6 +35,7 @@ inactivity_timeout => timeout(), initial_connection_window_size => 65535..16#7fffffff, initial_stream_window_size => 0..16#7fffffff, + linger_timeout => timeout(), logger => module(), max_concurrent_streams => non_neg_integer() | infinity, max_connection_buffer_size => non_neg_integer(), @@ -956,7 +957,7 @@ terminate(State=#state{socket=Socket, transport=Transport, http2_status=Status, end, terminate_all_streams(State, maps:to_list(Streams), Reason), cowboy_children:terminate(Children), - Transport:close(Socket), + terminate_linger(State), exit({shutdown, Reason}); terminate(#state{socket=Socket, transport=Transport}, Reason) -> Transport:close(Socket), @@ -973,6 +974,49 @@ terminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reas terminate_stream_handler(State, StreamID, Reason, StreamState), terminate_all_streams(State, Tail, Reason). +%% This code is copied from cowboy_http. +terminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) -> + case Transport:shutdown(Socket, write) of + ok -> + case maps:get(linger_timeout, Opts, 1000) of + 0 -> + ok; + infinity -> + terminate_linger_before_loop(State, undefined, Transport:messages()); + Timeout -> + TimerRef = erlang:start_timer(Timeout, self(), linger_timeout), + terminate_linger_before_loop(State, TimerRef, Transport:messages()) + end; + {error, _} -> + ok + end. + +terminate_linger_before_loop(State, TimerRef, Messages) -> + %% We may already be in active mode when we do this + %% but it's OK because we are shutting down anyway. + case setopts_active(State) of + ok -> + terminate_linger_loop(State, TimerRef, Messages); + {error, _} -> + ok + end. + +terminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) -> + receive + {OK, Socket, _} when OK =:= element(1, Messages) -> + terminate_linger_loop(State, TimerRef, Messages); + {Closed, Socket} when Closed =:= element(2, Messages) -> + ok; + {Error, Socket, _} when Error =:= element(3, Messages) -> + ok; + {Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive -> + terminate_linger_before_loop(State, TimerRef, Messages); + {timeout, TimerRef, linger_timeout} -> + ok; + _ -> + terminate_linger_loop(State, TimerRef, Messages) + end. + %% @todo Don't send an RST_STREAM if one was already sent. reset_stream(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0}, StreamID, Error) -> -- cgit v1.2.3