comparison src/http/v2/ngx_http_v2_filter_module.c @ 7975:a7a77549265e

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.
author Maxim Dounin <mdounin@mdounin.ru>
date Thu, 25 Nov 2021 22:02:10 +0300
parents 80359395b345
children 32b0ba4855a6
comparison
equal deleted inserted replaced
7974:555533169506 7975:a7a77549265e
1430 1430
1431 #if (NGX_SUPPRESS_WARN) 1431 #if (NGX_SUPPRESS_WARN)
1432 size = 0; 1432 size = 0;
1433 #endif 1433 #endif
1434 1434
1435 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
1436 "http2 send chain: %p", in);
1437
1435 while (in) { 1438 while (in) {
1436 size = ngx_buf_size(in->buf); 1439 size = ngx_buf_size(in->buf);
1437 1440
1438 if (size || in->buf->last_buf) { 1441 if (size || in->buf->last_buf) {
1439 break; 1442 break;
1448 ngx_log_error(NGX_LOG_ERR, fc->log, 0, 1451 ngx_log_error(NGX_LOG_ERR, fc->log, 0,
1449 "output on closed stream"); 1452 "output on closed stream");
1450 return NGX_CHAIN_ERROR; 1453 return NGX_CHAIN_ERROR;
1451 } 1454 }
1452 1455
1453 if (stream->queued) { 1456 if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
1457 return NGX_CHAIN_ERROR;
1458 }
1459
1460 return NULL;
1461 }
1462
1463 h2c = stream->connection;
1464
1465 if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
1466
1467 if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
1468 return NGX_CHAIN_ERROR;
1469 }
1470
1471 if (ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
1454 fc->write->active = 1; 1472 fc->write->active = 1;
1455 fc->write->ready = 0; 1473 fc->write->ready = 0;
1456 1474 return in;
1457 } else { 1475 }
1458 fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
1459 }
1460
1461 return NULL;
1462 }
1463
1464 h2c = stream->connection;
1465
1466 if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
1467 fc->write->active = 1;
1468 fc->write->ready = 0;
1469 return in;
1470 } 1476 }
1471 1477
1472 if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { 1478 if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
1473 cl = ngx_alloc_chain_link(r->pool); 1479 cl = ngx_alloc_chain_link(r->pool);
1474 if (cl == NULL) { 1480 if (cl == NULL) {
1807 1813
1808 1814
1809 static ngx_inline ngx_int_t 1815 static ngx_inline ngx_int_t
1810 ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream) 1816 ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream)
1811 { 1817 {
1818 if (stream->queued == 0) {
1819 fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
1820 return NGX_OK;
1821 }
1822
1812 stream->blocked = 1; 1823 stream->blocked = 1;
1813 1824
1814 if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) { 1825 if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) {
1815 fc->error = 1; 1826 fc->error = 1;
1816 return NGX_ERROR; 1827 return NGX_ERROR;