From 0e629f47990e0d6a87c2f023f01a00bae48406b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 16 May 2018 16:01:30 +0200 Subject: Add option linger_timeout to cowboy_http --- doc/src/manual/cowboy_http.asciidoc | 7 +++++++ src/cowboy_http.erl | 42 ++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/doc/src/manual/cowboy_http.asciidoc b/doc/src/manual/cowboy_http.asciidoc index e1936fc..21a6d65 100644 --- a/doc/src/manual/cowboy_http.asciidoc +++ b/doc/src/manual/cowboy_http.asciidoc @@ -21,6 +21,7 @@ opts() :: #{ env => cowboy_middleware:env(), idle_timeout => timeout(), inactivity_timeout => timeout(), + linger_timeout => timeout(), max_empty_lines => non_neg_integer(), max_header_name_length => non_neg_integer(), max_header_value_length => non_neg_integer(), @@ -59,6 +60,11 @@ idle_timeout (60000):: inactivity_timeout (300000):: 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):: Maximum number of empty lines before a request. @@ -98,6 +104,7 @@ stream_handlers ([cowboy_stream_h]):: == Changelog +* *2.5*: The `linger_timeout` 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 `idle_timeout`, `inactivity_timeout` and `shutdown_timeout` options were added. diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 440aadd..5890037 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -25,6 +25,7 @@ env => cowboy_middleware:env(), idle_timeout => timeout(), inactivity_timeout => timeout(), + linger_timeout => timeout(), max_empty_lines => non_neg_integer(), max_header_name_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(). terminate(undefined, Reason) -> exit({shutdown, Reason}); -terminate(#state{streams=Streams, children=Children}, Reason) -> +terminate(State=#state{streams=Streams, children=Children}, Reason) -> terminate_all_streams(Streams, Reason), cowboy_children:terminate(Children), + terminate_linger(State), exit({shutdown, Reason}). terminate_all_streams([], _) -> @@ -1247,6 +1249,44 @@ terminate_all_streams([#stream{id=StreamID, state=StreamState}|Tail], Reason) -> stream_call_terminate(StreamID, Reason, StreamState), 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. -spec system_continue(_, _, {#state{}, binary()}) -> ok. -- cgit v1.2.3