[PATCH] HTTP/2: fixed sendfile() aio handling
Sergey Kandaurov
pluknet at nginx.com
Tue Nov 16 11:59:46 UTC 2021
> On 11 Nov 2021, at 06:10, Maxim Dounin <mdounin at mdounin.ru> wrote:
>
> # HG changeset patch
> # User Maxim Dounin <mdounin at mdounin.ru>
> # Date 1636599377 -10800
> # Thu Nov 11 05:56:17 2021 +0300
> # Node ID 76e072a6947a221868705c13973de15319c0d921
> # Parent 82b750b20c5205d685e59031247fe898f011394e
> 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 processig
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 do not return EBUSY.
does not
>
> Fix is to post a write event on HTTP/2 connection in the thread event
> handler (and aio preload handler). This ensures that sendfile() will be
> completed and stream processing will be resumed by HTTP/2 code.
>
> diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c
> --- a/src/http/ngx_http_copy_filter_module.c
> +++ b/src/http/ngx_http_copy_filter_module.c
> @@ -250,6 +250,21 @@ ngx_http_copy_aio_sendfile_event_handler
> r->aio = 0;
> ev->complete = 0;
>
> +#if (NGX_HTTP_V2)
> +
> + if (r->stream) {
> + /*
> + * for HTTP/2, trigger a write event on the main connection
> + * to handle sendfile() preload
> + */
> +
> + ngx_post_event(r->stream->connection->connection->write,
> + &ngx_posted_events);
> + return;
> + }
> +
> +#endif
> +
> r->connection->write->handler(r->connection->write);
> }
>
> @@ -323,6 +338,20 @@ ngx_http_copy_thread_event_handler(ngx_e
> r->main->blocked--;
> r->aio = 0;
>
> +#if (NGX_HTTP_V2)
> +
> + if (r->stream) {
> + /*
> + * for HTTP/2, trigger a write event on the main connection
> + * to handle sendfile() in threads
> + */
> +
> + ngx_post_event(r->stream->connection->connection->write,
> + &ngx_posted_events);
> + }
> +
> +#endif
> +
This thread event handler is used not only for sendfile() completion,
but also to complete reading in threads a buffered upstream response.
In this case, posting a write event on HTTP/2 connection looks
unnecessary, since there is no sendfile() in action, it will do nothing.
On the other hand, if it is indeed used to complete a sendfile() task,
which needs to invoke http2 write handler, calling write_event_handler()
directly from thread event handler seems to be redundant: it could be
optimized away since http2 write handler will normally end up in posting
a write event on the main connection, anyway, see the call sequence
ngx_http_v2_write_handler() -> ngx_http_v2_send_output_queue()
-> ngx_http_v2_data_frame_handler() -> ngx_http_v2_handle_stream().
So, currently it looks as below.
2021/11/16 11:30:21 [debug] 3704204#3704206: run task #1 in thread pool "default"
2021/11/16 11:30:21 [debug] 3704204#3704206: linux sendfile thread handler
..
2021/11/16 11:30:21 [debug] 3704204#3704204: run completion handler for task #1
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http thread: "/1?" done:0
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 post event 00005613DD6E9A90
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http upstream process downstream
...
2021/11/16 11:30:21 [debug] 3704204#3704204: posted event 00005613DD6E9A90
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 delete posted event 00005613DD6E9A90
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http2 write handler
...
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http2:1 DATA frame 00005613DD596C88 was sent
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 post event 00005613DD58D9A0
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http2 frame sent: 00005613DD596C88 sid:1 bl:1 len:8192
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 update posted event 00005613DD58D9A0
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http2:1 DATA frame 00005613DD597750 was sent partially
2021/11/16 11:30:21 [debug] 3704204#3704204: posted event 00005613DD58D9A0
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 delete posted event 00005613DD58D9A0
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http run request: "/1?"
2021/11/16 11:30:21 [debug] 3704204#3704204: *1 http upstream process downstream
So, it could be narrowed down, something like the aio preload handler:
diff -r 76e072a6947a -r 5f48b9a797d1 src/http/ngx_http_copy_filter_module.c
--- a/src/http/ngx_http_copy_filter_module.c Thu Nov 11 05:56:17 2021 +0300
+++ b/src/http/ngx_http_copy_filter_module.c Mon Nov 15 21:04:26 2021 +0000
@@ -340,7 +340,7 @@
#if (NGX_HTTP_V2)
- if (r->stream) {
+ if (r->stream && r->stream->connection->connection->sendfile_task) {
/*
* for HTTP/2, trigger a write event on the main connection
* to handle sendfile() in threads
@@ -348,6 +348,7 @@
ngx_post_event(r->stream->connection->connection->write,
&ngx_posted_events);
+ return;
}
#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
> --- a/src/http/ngx_http_upstream.c
> +++ b/src/http/ngx_http_upstream.c
> @@ -3905,6 +3905,20 @@ ngx_http_upstream_thread_event_handler(n
> r->main->blocked--;
> r->aio = 0;
>
> +#if (NGX_HTTP_V2)
> +
> + if (r->stream) {
> + /*
> + * for HTTP/2, trigger a write event on the main connection
> + * to handle sendfile() in threads
> + */
> +
> + ngx_post_event(r->stream->connection->connection->write,
> + &ngx_posted_events);
> + }
> +
> +#endif
> +
> if (r->done) {
> /*
> * trigger connection event handler if the subrequest was
>
I could not figure out, how this part is related, since upstream
thread handler is only enabled with "aio_write on;" to write down
a buffered upstream response to disk. It doesn't seem to be used
with sendfile().
--
Sergey Kandaurov
More information about the nginx-devel
mailing list