]> git.kaiwu.me - nginx.git/commitdiff
HTTP/2: fixed sendfile() aio handling.
authorMaxim Dounin <mdounin@mdounin.ru>
Thu, 25 Nov 2021 19:02:10 +0000 (22:02 +0300)
committerMaxim Dounin <mdounin@mdounin.ru>
Thu, 25 Nov 2021 19:02:10 +0000 (22:02 +0300)
With sendfile() in threads ("aio threads; sendfile on;"), client connection
can block on writing, waiting for sendfile() to complete.  In HTTP/2 this
might result in the request hang, since an attempt to continue processing
in thread event handler will call request's write event handler, which
is usually stopped by ngx_http_v2_send_chain(): it does nothing if there
are no additional data and stream->queued is set.  Further, HTTP/2 resets
stream's c->write->ready to 0 if writing blocks, so just fixing
ngx_http_v2_send_chain() is not enough.

Can be reproduced with test suite on Linux with:

TEST_NGINX_GLOBALS_HTTP="aio threads; sendfile on;" prove h2*.t

The following tests currently fail: h2_keepalive.t, h2_priority.t,
h2_proxy_max_temp_file_size.t, h2.t, h2_trailers.t.

Similarly, sendfile() with AIO preloading on FreeBSD can block as well,
with similar results.  This is, however, harder to reproduce, especially
on modern FreeBSD systems, since sendfile() usually does not return EBUSY.

Fix is to modify ngx_http_v2_send_chain() so it actually tries to send
data to the main connection when called, and to make sure that
c->write->ready is set by the relevant event handlers.

src/http/ngx_http_copy_filter_module.c
src/http/ngx_http_upstream.c
src/http/v2/ngx_http_v2_filter_module.c

index 6c93a3bd9e6f0c568ef2ac3281c299953b4fb584..b47e4afa750838dd023fb7429256f5f1ab567be8 100644 (file)
@@ -253,16 +253,32 @@ static void
 ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev)
 {
     ngx_event_aio_t     *aio;
+    ngx_connection_t    *c;
     ngx_http_request_t  *r;
 
     aio = ev->data;
     r = aio->data;
+    c = r->connection;
 
     r->main->blocked--;
     r->aio = 0;
     ev->complete = 0;
 
-    r->connection->write->handler(r->connection->write);
+#if (NGX_HTTP_V2)
+
+    if (r->stream) {
+        /*
+         * for HTTP/2, update write event to make sure processing will
+         * reach the main connection to handle sendfile() preload
+         */
+
+        c->write->ready = 1;
+        c->write->active = 0;
+    }
+
+#endif
+
+    c->write->handler(c->write);
 }
 
 #endif
@@ -357,6 +373,20 @@ ngx_http_copy_thread_event_handler(ngx_event_t *ev)
     r->main->blocked--;
     r->aio = 0;
 
+#if (NGX_HTTP_V2)
+
+    if (r->stream) {
+        /*
+         * for HTTP/2, update write event to make sure processing will
+         * reach the main connection to handle sendfile() in threads
+         */
+
+        c->write->ready = 1;
+        c->write->active = 0;
+    }
+
+#endif
+
     if (r->done) {
         /*
          * trigger connection event handler if the subrequest was
index e228493421f4b24639324606d568a3debd15a601..ded833c41612a7ba918c3a067f5c70619003d0b1 100644 (file)
@@ -3927,6 +3927,20 @@ ngx_http_upstream_thread_event_handler(ngx_event_t *ev)
     r->main->blocked--;
     r->aio = 0;
 
+#if (NGX_HTTP_V2)
+
+    if (r->stream) {
+        /*
+         * for HTTP/2, update write event to make sure processing will
+         * reach the main connection to handle sendfile() in threads
+         */
+
+        c->write->ready = 1;
+        c->write->active = 0;
+    }
+
+#endif
+
     if (r->done) {
         /*
          * trigger connection event handler if the subrequest was
index a6e5e7d4f7b1531eca0b71f4f5299c0ad6e6da46..9ffb155dfd16343d7117236ffd8873838759385c 100644 (file)
@@ -1432,6 +1432,9 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
     size = 0;
 #endif
 
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 send chain: %p", in);
+
     while (in) {
         size = ngx_buf_size(in->buf);
 
@@ -1450,12 +1453,8 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
             return NGX_CHAIN_ERROR;
         }
 
-        if (stream->queued) {
-            fc->write->active = 1;
-            fc->write->ready = 0;
-
-        } else {
-            fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
+        if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
         }
 
         return NULL;
@@ -1464,9 +1463,16 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
     h2c = stream->connection;
 
     if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
-        fc->write->active = 1;
-        fc->write->ready = 0;
-        return in;
+
+        if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
+            fc->write->active = 1;
+            fc->write->ready = 0;
+            return in;
+        }
     }
 
     if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
@@ -1809,6 +1815,11 @@ ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
 static ngx_inline ngx_int_t
 ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream)
 {
+    if (stream->queued == 0) {
+        fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
+        return NGX_OK;
+    }
+
     stream->blocked = 1;
 
     if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) {