summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Dounin <mdounin@mdounin.ru>2021-11-25 22:02:10 +0300
committerMaxim Dounin <mdounin@mdounin.ru>2021-11-25 22:02:10 +0300
commit83e92a2edd6bf7c6867b653284ac44962c4e33c9 (patch)
tree55673052e76b4d5f7002d61ae9b94db0e0f6fa41
parent2361e98a34ca3b66c45fbb4e906401b430e41013 (diff)
downloadnginx-83e92a2edd6bf7c6867b653284ac44962c4e33c9.tar.gz
HTTP/2: fixed sendfile() aio handling.
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.
-rw-r--r--src/http/ngx_http_copy_filter_module.c32
-rw-r--r--src/http/ngx_http_upstream.c14
-rw-r--r--src/http/v2/ngx_http_v2_filter_module.c29
3 files changed, 65 insertions, 10 deletions
diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c
index 6c93a3bd9..b47e4afa7 100644
--- a/src/http/ngx_http_copy_filter_module.c
+++ b/src/http/ngx_http_copy_filter_module.c
@@ -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
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
index e22849342..ded833c41 100644
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -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
diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c
index a6e5e7d4f..9ffb155df 100644
--- a/src/http/v2/ngx_http_v2_filter_module.c
+++ b/src/http/v2/ngx_http_v2_filter_module.c
@@ -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) {