Mercurial > hg > nginx
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 |