Skip to content

Commit

Permalink
HTTP/2: fixed sendfile() aio handling.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mdounin committed Nov 25, 2021
1 parent 2361e98 commit 83e92a2
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 10 deletions.
32 changes: 31 additions & 1 deletion src/http/ngx_http_copy_filter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions src/http/ngx_http_upstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 20 additions & 9 deletions src/http/v2/ngx_http_v2_filter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 83e92a2

Please sign in to comment.