[nginx] HTTP/2: lingering close after GOAWAY.

Ruslan Ermilov ru at nginx.com
Fri Jul 3 13:24:19 UTC 2020


details:   https://hg.nginx.org/nginx/rev/c5840ca2063d
branches:  
changeset: 7673:c5840ca2063d
user:      Ruslan Ermilov <ru at nginx.com>
date:      Fri Jul 03 16:16:47 2020 +0300
description:
HTTP/2: lingering close after GOAWAY.

After sending the GOAWAY frame, a connection is now closed using
the lingering close mechanism.

This allows for the reliable delivery of the GOAWAY frames, while
also fixing connection resets observed when http2_max_requests is
reached (ticket #1250), or with graceful shutdown (ticket #1544),
when some additional data from the client is received on a fully
closed connection.

For HTTP/2, the settings lingering_close, lingering_timeout, and
lingering_time are taken from the "server" level.

diffstat:

 src/http/v2/ngx_http_v2.c |  128 +++++++++++++++++++++++++++++++++++++++++++--
 src/http/v2/ngx_http_v2.h |    2 +
 2 files changed, 124 insertions(+), 6 deletions(-)

diffs (187 lines):

diff -r 3dcb1aba894a -r c5840ca2063d src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c	Mon Jun 29 17:15:51 2020 +0300
+++ b/src/http/v2/ngx_http_v2.c	Fri Jul 03 16:16:47 2020 +0300
@@ -60,6 +60,8 @@ typedef struct {
 static void ngx_http_v2_read_handler(ngx_event_t *rev);
 static void ngx_http_v2_write_handler(ngx_event_t *wev);
 static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c);
+static void ngx_http_v2_lingering_close(ngx_http_v2_connection_t *h2c);
+static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev);
 
 static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c,
     u_char *pos, u_char *end);
@@ -661,7 +663,7 @@ ngx_http_v2_handle_connection(ngx_http_v
     }
 
     if (h2c->goaway) {
-        ngx_http_close_connection(c);
+        ngx_http_v2_lingering_close(h2c);
         return;
     }
 
@@ -699,6 +701,113 @@ ngx_http_v2_handle_connection(ngx_http_v
 }
 
 
+static void
+ngx_http_v2_lingering_close(ngx_http_v2_connection_t *h2c)
+{
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = h2c->connection;
+
+    clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx,
+                                        ngx_http_core_module);
+
+    if (clcf->lingering_close == NGX_HTTP_LINGERING_OFF) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    rev = c->read;
+    rev->handler = ngx_http_v2_lingering_close_handler;
+
+    h2c->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000);
+    ngx_add_timer(rev, clcf->lingering_timeout);
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    wev = c->write;
+    wev->handler = ngx_http_empty_handler;
+
+    if (wev->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) {
+        if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) {
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+    if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
+        ngx_connection_error(c, ngx_socket_errno,
+                             ngx_shutdown_socket_n " failed");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (rev->ready) {
+        ngx_http_v2_lingering_close_handler(rev);
+    }
+}
+
+
+static void
+ngx_http_v2_lingering_close_handler(ngx_event_t *rev)
+{
+    ssize_t                    n;
+    ngx_msec_t                 timer;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_v2_connection_t  *h2c;
+    u_char                     buffer[NGX_HTTP_LINGERING_BUFFER_SIZE];
+
+    c = rev->data;
+    h2c = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http2 lingering close handler");
+
+    if (rev->timedout) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    timer = (ngx_msec_t) h2c->lingering_time - (ngx_msec_t) ngx_time();
+    if ((ngx_msec_int_t) timer <= 0) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    do {
+        n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %z", n);
+
+        if (n == NGX_ERROR || n == 0) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+    } while (rev->ready);
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx,
+                                        ngx_http_core_module);
+    timer *= 1000;
+
+    if (timer > clcf->lingering_timeout) {
+        timer = clcf->lingering_timeout;
+    }
+
+    ngx_add_timer(rev, timer);
+}
+
+
 static u_char *
 ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos,
     u_char *end)
@@ -4541,16 +4650,15 @@ ngx_http_v2_finalize_connection(ngx_http
     h2c->blocked = 1;
 
     if (!c->error && !h2c->goaway) {
+        h2c->goaway = 1;
+
         if (ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) {
             (void) ngx_http_v2_send_output_queue(h2c);
         }
     }
 
-    c->error = 1;
-
     if (!h2c->processing && !h2c->pushing) {
-        ngx_http_close_connection(c);
-        return;
+        goto done;
     }
 
     c->read->handler = ngx_http_empty_handler;
@@ -4598,10 +4706,18 @@ ngx_http_v2_finalize_connection(ngx_http
     h2c->blocked = 0;
 
     if (h2c->processing || h2c->pushing) {
+        c->error = 1;
         return;
     }
 
-    ngx_http_close_connection(c);
+done:
+
+    if (c->error) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_http_v2_lingering_close(h2c);
 }
 
 
diff -r 3dcb1aba894a -r c5840ca2063d src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h	Mon Jun 29 17:15:51 2020 +0300
+++ b/src/http/v2/ngx_http_v2.h	Fri Jul 03 16:16:47 2020 +0300
@@ -157,6 +157,8 @@ struct ngx_http_v2_connection_s {
     ngx_uint_t                       last_sid;
     ngx_uint_t                       last_push;
 
+    time_t                           lingering_time;
+
     unsigned                         closed_nodes:8;
     unsigned                         settings_ack:1;
     unsigned                         table_update:1;


More information about the nginx-devel mailing list