comparison src/http/v2/ngx_http_v2_filter_module.c @ 6833:3834951e32ab

HTTP/2: fixed posted streams handling. A bug was introduced by 82efcedb310b that could lead to timing out of responses or segmentation fault, when accept_mutex was enabled. The output queue in HTTP/2 can contain frames from different streams. When the queue is sent, all related write handlers need to be called. In order to do so, the streams were added to the h2c->posted queue after handling sent frames. Then this queue was processed in ngx_http_v2_write_handler(). If accept_mutex is enabled, the event's "ready" flag is set but its handler is not called immediately. Instead, the event is added to the ngx_posted_events queue. At the same time in this queue can be events from upstream connections. Such events can result in sending output queue before ngx_http_v2_write_handler() is triggered. And at the time ngx_http_v2_write_handler() is called, the output queue can be already empty with some streams added to h2c->posted. But after 82efcedb310b, these streams weren't processed if all frames have already been sent and the output queue was empty. This might lead to a situation when a number of streams were get stuck in h2c->posted queue for a long time. Eventually these streams might get closed by the send timeout. In the worst case this might also lead to a segmentation fault, if already freed stream was left in the h2c->posted queue. This could happen if one of the streams was terminated but wasn't closed, due to the HEADERS frame or a partially sent DATA frame left in the output queue. If this happened the ngx_http_v2_filter_cleanup() handler removed the stream from the h2c->waiting or h2c->posted queue on termination stage, before the frame has been sent, and the stream was again added to the h2c->posted queue after the frame was sent. In order to fix all these problems and simplify the code, write events of fake stream connections are now added to ngx_posted_events instead of using a custom h2c->posted queue.
author Valentin Bartenev <vbart@nginx.com>
date Mon, 28 Nov 2016 20:58:14 +0300
parents 45d553812055
children d15172ebb400
comparison
equal deleted inserted replaced
6832:ec10ce307dc0 6833:3834951e32ab
1102 ngx_http_v2_stream_t *stream) 1102 ngx_http_v2_stream_t *stream)
1103 { 1103 {
1104 ngx_queue_t *q; 1104 ngx_queue_t *q;
1105 ngx_http_v2_stream_t *s; 1105 ngx_http_v2_stream_t *s;
1106 1106
1107 if (stream->handled) { 1107 if (stream->waiting) {
1108 return; 1108 return;
1109 } 1109 }
1110 1110
1111 stream->handled = 1; 1111 stream->waiting = 1;
1112 1112
1113 for (q = ngx_queue_last(&h2c->waiting); 1113 for (q = ngx_queue_last(&h2c->waiting);
1114 q != ngx_queue_sentinel(&h2c->waiting); 1114 q != ngx_queue_sentinel(&h2c->waiting);
1115 q = ngx_queue_prev(q)) 1115 q = ngx_queue_prev(q))
1116 { 1116 {
1296 1296
1297 static ngx_inline void 1297 static ngx_inline void
1298 ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c, 1298 ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c,
1299 ngx_http_v2_stream_t *stream) 1299 ngx_http_v2_stream_t *stream)
1300 { 1300 {
1301 ngx_event_t *wev;
1301 ngx_connection_t *fc; 1302 ngx_connection_t *fc;
1302 1303
1303 if (stream->handled || stream->blocked) { 1304 if (stream->waiting || stream->blocked) {
1304 return; 1305 return;
1305 } 1306 }
1306 1307
1307 fc = stream->request->connection; 1308 fc = stream->request->connection;
1308 1309
1309 if (!fc->error && (stream->exhausted || fc->write->delayed)) { 1310 if (!fc->error && stream->exhausted) {
1310 return; 1311 return;
1311 } 1312 }
1312 1313
1313 stream->handled = 1; 1314 wev = fc->write;
1314 ngx_queue_insert_tail(&h2c->posted, &stream->queue); 1315
1316 wev->active = 0;
1317 wev->ready = 1;
1318
1319 if (!fc->error && wev->delayed) {
1320 return;
1321 }
1322
1323 ngx_post_event(wev, &ngx_posted_events);
1315 } 1324 }
1316 1325
1317 1326
1318 static void 1327 static void
1319 ngx_http_v2_filter_cleanup(void *data) 1328 ngx_http_v2_filter_cleanup(void *data)
1320 { 1329 {
1321 ngx_http_v2_stream_t *stream = data; 1330 ngx_http_v2_stream_t *stream = data;
1322 1331
1323 size_t window; 1332 size_t window;
1333 ngx_event_t *wev;
1334 ngx_queue_t *q;
1324 ngx_http_v2_out_frame_t *frame, **fn; 1335 ngx_http_v2_out_frame_t *frame, **fn;
1325 ngx_http_v2_connection_t *h2c; 1336 ngx_http_v2_connection_t *h2c;
1326 1337
1327 if (stream->handled) { 1338 if (stream->waiting) {
1328 stream->handled = 0; 1339 stream->waiting = 0;
1329 ngx_queue_remove(&stream->queue); 1340 ngx_queue_remove(&stream->queue);
1330 } 1341 }
1331 1342
1332 if (stream->queued == 0) { 1343 if (stream->queued == 0) {
1333 return; 1344 return;
1357 } 1368 }
1358 1369
1359 fn = &frame->next; 1370 fn = &frame->next;
1360 } 1371 }
1361 1372
1362 if (h2c->send_window == 0 && window && !ngx_queue_empty(&h2c->waiting)) { 1373 if (h2c->send_window == 0 && window) {
1363 ngx_queue_add(&h2c->posted, &h2c->waiting); 1374
1364 ngx_queue_init(&h2c->waiting); 1375 while (!ngx_queue_empty(&h2c->waiting)) {
1376 q = ngx_queue_head(&h2c->waiting);
1377
1378 ngx_queue_remove(q);
1379
1380 stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
1381
1382 stream->waiting = 0;
1383
1384 wev = stream->request->connection->write;
1385
1386 wev->active = 0;
1387 wev->ready = 1;
1388
1389 if (!wev->delayed) {
1390 ngx_post_event(wev, &ngx_posted_events);
1391 }
1392 }
1365 } 1393 }
1366 1394
1367 h2c->send_window += window; 1395 h2c->send_window += window;
1368 } 1396 }
1369 1397