Browse Source

Add option linger_timeout to cowboy_http

Loïc Hoguin 7 years ago
parent
commit
0e629f4799
2 changed files with 48 additions and 1 deletions
  1. 7 0
      doc/src/manual/cowboy_http.asciidoc
  2. 41 1
      src/cowboy_http.erl

+ 7 - 0
doc/src/manual/cowboy_http.asciidoc

@@ -21,6 +21,7 @@ opts() :: #{
     env                     => cowboy_middleware:env(),
     env                     => cowboy_middleware:env(),
     idle_timeout            => timeout(),
     idle_timeout            => timeout(),
     inactivity_timeout      => timeout(),
     inactivity_timeout      => timeout(),
+    linger_timeout          => timeout(),
     max_empty_lines         => non_neg_integer(),
     max_empty_lines         => non_neg_integer(),
     max_header_name_length  => non_neg_integer(),
     max_header_name_length  => non_neg_integer(),
     max_header_value_length => non_neg_integer(),
     max_header_value_length => non_neg_integer(),
@@ -59,6 +60,11 @@ idle_timeout (60000)::
 inactivity_timeout (300000)::
 inactivity_timeout (300000)::
     Time in ms with nothing received at all before Cowboy closes the connection.
     Time in ms with nothing received at all before Cowboy closes the connection.
 
 
+linger_timeout (1000)::
+    Time in ms that Cowboy will wait when closing the connection. This is
+    necessary to avoid the TCP reset problem as described in the
+    https://tools.ietf.org/html/rfc7230#section-6.6[section 6.6 of RFC7230].
+
 max_empty_lines (5)::
 max_empty_lines (5)::
     Maximum number of empty lines before a request.
     Maximum number of empty lines before a request.
 
 
@@ -98,6 +104,7 @@ stream_handlers ([cowboy_stream_h])::
 
 
 == Changelog
 == Changelog
 
 
+* *2.5*: The `linger_timeout` option was added.
 * *2.2*: The `max_skip_body_length` option was added.
 * *2.2*: The `max_skip_body_length` option was added.
 * *2.0*: The `timeout` option was renamed `request_timeout`.
 * *2.0*: The `timeout` option was renamed `request_timeout`.
 * *2.0*: The `idle_timeout`, `inactivity_timeout` and `shutdown_timeout` options were added.
 * *2.0*: The `idle_timeout`, `inactivity_timeout` and `shutdown_timeout` options were added.

+ 41 - 1
src/cowboy_http.erl

@@ -25,6 +25,7 @@
 	env => cowboy_middleware:env(),
 	env => cowboy_middleware:env(),
 	idle_timeout => timeout(),
 	idle_timeout => timeout(),
 	inactivity_timeout => timeout(),
 	inactivity_timeout => timeout(),
+	linger_timeout => timeout(),
 	max_empty_lines => non_neg_integer(),
 	max_empty_lines => non_neg_integer(),
 	max_header_name_length => non_neg_integer(),
 	max_header_name_length => non_neg_integer(),
 	max_header_value_length => non_neg_integer(),
 	max_header_value_length => non_neg_integer(),
@@ -1236,9 +1237,10 @@ early_error(StatusCode0, #state{socket=Socket, transport=Transport,
 -spec terminate(_, _) -> no_return().
 -spec terminate(_, _) -> no_return().
 terminate(undefined, Reason) ->
 terminate(undefined, Reason) ->
 	exit({shutdown, Reason});
 	exit({shutdown, Reason});
-terminate(#state{streams=Streams, children=Children}, Reason) ->
+terminate(State=#state{streams=Streams, children=Children}, Reason) ->
 	terminate_all_streams(Streams, Reason),
 	terminate_all_streams(Streams, Reason),
 	cowboy_children:terminate(Children),
 	cowboy_children:terminate(Children),
+	terminate_linger(State),
 	exit({shutdown, Reason}).
 	exit({shutdown, Reason}).
 
 
 terminate_all_streams([], _) ->
 terminate_all_streams([], _) ->
@@ -1247,6 +1249,44 @@ terminate_all_streams([#stream{id=StreamID, state=StreamState}|Tail], Reason) ->
 	stream_call_terminate(StreamID, Reason, StreamState),
 	stream_call_terminate(StreamID, Reason, StreamState),
 	terminate_all_streams(Tail, Reason).
 	terminate_all_streams(Tail, Reason).
 
 
+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_loop(State, undefined);
+				Timeout ->
+					TimerRef = erlang:start_timer(Timeout, self(), linger_timeout),
+					terminate_linger_loop(State, TimerRef)
+			end;
+		{error, _} ->
+			ok
+	end.
+
+terminate_linger_loop(State=#state{socket=Socket, transport=Transport}, TimerRef) ->
+	{OK, Closed, Error} = Transport:messages(),
+	%% We may already have a message in the mailbox when we do this
+	%% but it's OK because we are shutting down anyway.
+	case Transport:setopts(Socket, [{active, once}]) of
+		ok ->
+			receive
+				{OK, Socket, _} ->
+					terminate_linger_loop(State, TimerRef);
+				{Closed, Socket} ->
+					ok;
+				{Error, Socket, _} ->
+					ok;
+				{timeout, TimerRef, linger_timeout} ->
+					ok;
+				_ ->
+					terminate_linger_loop(State, TimerRef)
+			end;
+		{error, _} ->
+			ok
+	end.
+
 %% System callbacks.
 %% System callbacks.
 
 
 -spec system_continue(_, _, {#state{}, binary()}) -> ok.
 -spec system_continue(_, _, {#state{}, binary()}) -> ok.