#1106 closed defect (fixed)
Stale workers not exiting after reload (with HTTP/2 long poll requests)
| Reported by: | Owned by: | ||
|---|---|---|---|
| Priority: | minor | Milestone: | |
| Component: | nginx-core | Version: | 1.11.x |
| Keywords: | HTTP2, longpoll, keepalive | Cc: | |
| uname -a: | 3.16.0-60-generic 80~14.04.1-Ubuntu | ||
| nginx -V: | built by gcc 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) built with OpenSSL 1.0.2j 26 Sep 2016 TLS SNI support enabled configure arguments: --with-http_ssl_module --with-http_v2_module --with-openssl=/PATH/TO/OPENSSL/openssl-1.0.2j | ||
Description
Nginx stale workers not exiting with HTTP/2 long poll requests.
Based on the commit info in http://hg.nginx.org/nginx/rev/ea284434db0f, it seems that connections with active streams should be closed if nginx is exiting (e.g. after a reload). But for long poll requests, where there could always be active streams in the connection, it seems that ngx_http_v2_finalize_connection() (and maybe also ngx_http_v2_handle_connection_handler()) may not have a chance to be reached because of the non-zero h2c->processing value.
The issue can be reproduced by sending long poll HTTP/2 requests to Nginx.
- Enable HTTP/2 on an Nginx server.
- Configure the upstream server to return the response X seconds after receiving the request.
- Generate HTTP/2 traffic using tools such as
h2load. - Reload Nginx after seeing some number of requests.
- It can be observed that requests are still sent to the old worker (include
pidin the access log), and the stale worker would not exit.
Change History (6)
comment:1 by , 9 years ago
comment:2 by , 9 years ago
For now we are workarounding that problem by sending GOAWAY frame(via ngx_http_v2_send_goaway) to active connections during shutdown. Not sure if it's the proper solution for the given problem though.
comment:3 by , 9 years ago
| Status: | new → accepted |
|---|
comment:4 by , 9 years ago
Please, try the following patch:
diff -r 7cdf69d012f0 src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c Tue Oct 11 18:03:01 2016 +0300
+++ b/src/http/v2/ngx_http_v2.c Mon Oct 17 20:08:45 2016 +0300
@@ -136,6 +136,8 @@ static ngx_int_t ngx_http_v2_send_window
ngx_uint_t sid, size_t window);
static ngx_int_t ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c,
ngx_uint_t sid, ngx_uint_t status);
+static ngx_int_t ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c,
+ ngx_uint_t status);
static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame(
ngx_http_v2_connection_t *h2c, size_t length, ngx_uint_t type,
@@ -293,6 +295,8 @@ ngx_http_v2_init(ngx_event_t *rev)
rev->handler = ngx_http_v2_read_handler;
c->write->handler = ngx_http_v2_write_handler;
+ c->idle = 1;
+
ngx_http_v2_read_handler(rev);
}
@@ -320,6 +324,25 @@ ngx_http_v2_read_handler(ngx_event_t *re
h2c->blocked = 1;
+ if (c->close) {
+ c->close = 0;
+
+ if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) == NGX_ERROR) {
+ ngx_http_v2_finalize_connection(h2c, 0);
+ return;
+ }
+
+ if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) {
+ ngx_http_v2_finalize_connection(h2c, 0);
+ return;
+ }
+
+ h2c->goaway = 1;
+ h2c->blocked = 0;
+
+ return;
+ }
+
h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx,
ngx_http_v2_module);
@@ -633,6 +656,11 @@ ngx_http_v2_handle_connection(ngx_http_v
/* rc == NGX_OK */
}
+ if (h2c->goaway) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
ngx_http_v2_module);
if (h2c->state.incomplete) {
@@ -640,11 +668,6 @@ ngx_http_v2_handle_connection(ngx_http_v
return;
}
- if (ngx_terminate || ngx_exiting) {
- ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR);
- return;
- }
-
ngx_destroy_pool(h2c->pool);
h2c->pool = NULL;
@@ -658,7 +681,6 @@ ngx_http_v2_handle_connection(ngx_http_v
#endif
c->destroyed = 1;
- c->idle = 1;
ngx_reusable_connection(c, 1);
c->write->handler = ngx_http_empty_handler;
@@ -1027,6 +1049,12 @@ ngx_http_v2_state_headers(ngx_http_v2_co
return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
}
+ if (h2c->goaway) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "skipping HEADERS frame");
+ return ngx_http_v2_state_skip(h2c, pos, end);
+ }
+
if ((size_t) (end - pos) < size) {
return ngx_http_v2_state_save(h2c, pos, end,
ngx_http_v2_state_headers);
@@ -4162,7 +4190,6 @@ ngx_http_v2_idle_handler(ngx_event_t *re
#endif
c->destroyed = 0;
- c->idle = 0;
ngx_reusable_connection(c, 0);
h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
@@ -4197,8 +4224,10 @@ ngx_http_v2_finalize_connection(ngx_http
h2c->blocked = 1;
- if (!c->error && ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) {
- (void) ngx_http_v2_send_output_queue(h2c);
+ if (!c->error && !h2c->goaway) {
+ if (ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) {
+ (void) ngx_http_v2_send_output_queue(h2c);
+ }
}
c->error = 1;
diff -r 7cdf69d012f0 src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h Tue Oct 11 18:03:01 2016 +0300
+++ b/src/http/v2/ngx_http_v2.h Mon Oct 17 20:08:45 2016 +0300
@@ -146,6 +146,7 @@ struct ngx_http_v2_connection_s {
unsigned closed_nodes:8;
unsigned settings_ack:1;
unsigned blocked:1;
+ unsigned goaway:1;
};
comment:5 by , 9 years ago
| Owner: | set to |
|---|---|
| Resolution: | → fixed |
| Status: | accepted → closed |

You can reproduce that easily running h2load with 2+ concurrent clients against the following upstream:
package main import ( "fmt" "log" "net/http" "time" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { time.Sleep(60 * time.Second) fmt.Fprintf(w, "Hello, longpoll") }) log.Fatal(http.ListenAndServe(":8080", nil)) }If you reload nginx during that test, it'll never exit.