Fix receiving stream data stall
Previously, if automatic window update is enabled (which is default), after window size is set to 0 by nghttp2_session_set_local_window_size, once the receiving window is exhausted, even after window size is increased by nghttp2_session_set_local_window_size, no more data cannot be received. This is because nghttp2_session_set_local_window_size does not submit WINDOW_UPDATE. It is only triggered when new data arrives but since window is filled up, no more data cannot be received, thus dead lock happens. This commit fixes this issue. nghttp2_session_set_local_window_size submits WINDOW_UPDATE if necessary. https://github.com/curl/curl/issues/4939
This commit is contained in:
parent
459df42b8b
commit
2ec585518e
|
@ -2494,14 +2494,6 @@ static int session_update_stream_consumed_size(nghttp2_session *session,
|
|||
static int session_update_connection_consumed_size(nghttp2_session *session,
|
||||
size_t delta_size);
|
||||
|
||||
static int session_update_recv_connection_window_size(nghttp2_session *session,
|
||||
size_t delta_size);
|
||||
|
||||
static int session_update_recv_stream_window_size(nghttp2_session *session,
|
||||
nghttp2_stream *stream,
|
||||
size_t delta_size,
|
||||
int send_window_update);
|
||||
|
||||
/*
|
||||
* Called after a frame is sent. This function runs
|
||||
* on_frame_send_callback and handles stream closure upon END_STREAM
|
||||
|
@ -2735,7 +2727,7 @@ static int session_after_frame_sent1(nghttp2_session *session) {
|
|||
if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
|
||||
rv = session_update_connection_consumed_size(session, 0);
|
||||
} else {
|
||||
rv = session_update_recv_connection_window_size(session, 0);
|
||||
rv = nghttp2_session_update_recv_connection_window_size(session, 0);
|
||||
}
|
||||
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
|
@ -2761,7 +2753,8 @@ static int session_after_frame_sent1(nghttp2_session *session) {
|
|||
if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
|
||||
rv = session_update_stream_consumed_size(session, stream, 0);
|
||||
} else {
|
||||
rv = session_update_recv_stream_window_size(session, stream, 0, 1);
|
||||
rv =
|
||||
nghttp2_session_update_recv_stream_window_size(session, stream, 0, 1);
|
||||
}
|
||||
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
|
@ -5019,19 +5012,7 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accumulates received bytes |delta_size| for stream-level flow
|
||||
* control and decides whether to send WINDOW_UPDATE to that stream.
|
||||
* If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
|
||||
* be sent.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
static int session_update_recv_stream_window_size(nghttp2_session *session,
|
||||
int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session,
|
||||
nghttp2_stream *stream,
|
||||
size_t delta_size,
|
||||
int send_window_update) {
|
||||
|
@ -5063,19 +5044,7 @@ static int session_update_recv_stream_window_size(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accumulates received bytes |delta_size| for connection-level flow
|
||||
* control and decides whether to send WINDOW_UPDATE to the
|
||||
* connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
|
||||
* WINDOW_UPDATE will not be sent.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
static int session_update_recv_connection_window_size(nghttp2_session *session,
|
||||
int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session,
|
||||
size_t delta_size) {
|
||||
int rv;
|
||||
rv = adjust_recv_window_size(&session->recv_window_size, delta_size,
|
||||
|
@ -6454,7 +6423,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
}
|
||||
|
||||
/* Pad Length field is subject to flow control */
|
||||
rv = session_update_recv_connection_window_size(session, readlen);
|
||||
rv = nghttp2_session_update_recv_connection_window_size(session, readlen);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -6477,7 +6446,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
|
||||
stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id);
|
||||
if (stream) {
|
||||
rv = session_update_recv_stream_window_size(
|
||||
rv = nghttp2_session_update_recv_stream_window_size(
|
||||
session, stream, readlen,
|
||||
iframe->payloadleft ||
|
||||
(iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0);
|
||||
|
@ -6524,7 +6493,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
if (readlen > 0) {
|
||||
ssize_t data_readlen;
|
||||
|
||||
rv = session_update_recv_connection_window_size(session, readlen);
|
||||
rv = nghttp2_session_update_recv_connection_window_size(session,
|
||||
readlen);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -6533,7 +6503,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
return (ssize_t)inlen;
|
||||
}
|
||||
|
||||
rv = session_update_recv_stream_window_size(
|
||||
rv = nghttp2_session_update_recv_stream_window_size(
|
||||
session, stream, readlen,
|
||||
iframe->payloadleft ||
|
||||
(iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0);
|
||||
|
@ -6634,7 +6604,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
if (readlen > 0) {
|
||||
/* Update connection-level flow control window for ignored
|
||||
DATA frame too */
|
||||
rv = session_update_recv_connection_window_size(session, readlen);
|
||||
rv = nghttp2_session_update_recv_connection_window_size(session,
|
||||
readlen);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -898,4 +898,36 @@ int nghttp2_session_terminate_session_with_reason(nghttp2_session *session,
|
|||
uint32_t error_code,
|
||||
const char *reason);
|
||||
|
||||
/*
|
||||
* Accumulates received bytes |delta_size| for connection-level flow
|
||||
* control and decides whether to send WINDOW_UPDATE to the
|
||||
* connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
|
||||
* WINDOW_UPDATE will not be sent.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session,
|
||||
size_t delta_size);
|
||||
|
||||
/*
|
||||
* Accumulates received bytes |delta_size| for stream-level flow
|
||||
* control and decides whether to send WINDOW_UPDATE to that stream.
|
||||
* If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
|
||||
* be sent.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session,
|
||||
nghttp2_stream *stream,
|
||||
size_t delta_size,
|
||||
int send_window_update);
|
||||
|
||||
#endif /* NGHTTP2_SESSION_H */
|
||||
|
|
|
@ -450,6 +450,13 @@ int nghttp2_session_set_local_window_size(nghttp2_session *session,
|
|||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (window_size_increment > 0) {
|
||||
return nghttp2_session_add_window_update(session, 0, stream_id,
|
||||
window_size_increment);
|
||||
}
|
||||
|
||||
return nghttp2_session_update_recv_connection_window_size(session, 0);
|
||||
} else {
|
||||
stream = nghttp2_session_get_stream(session, stream_id);
|
||||
|
||||
|
@ -476,13 +483,16 @@ int nghttp2_session_set_local_window_size(nghttp2_session *session,
|
|||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (window_size_increment > 0) {
|
||||
return nghttp2_session_add_window_update(session, 0, stream_id,
|
||||
window_size_increment);
|
||||
}
|
||||
|
||||
return nghttp2_session_update_recv_stream_window_size(session, stream, 0,
|
||||
1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -10468,6 +10468,62 @@ void test_nghttp2_session_set_local_window_size(void) {
|
|||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
|
||||
nghttp2_session_del(session);
|
||||
|
||||
/* Make sure that nghttp2_session_set_local_window_size submits
|
||||
WINDOW_UPDATE if necessary to increase stream-level window. */
|
||||
nghttp2_session_client_new(&session, &callbacks, NULL);
|
||||
stream = open_sent_stream(session, 1);
|
||||
stream->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_set_local_window_size(
|
||||
session, NGHTTP2_FLAG_NONE, 1, 0));
|
||||
CU_ASSERT(0 == stream->recv_window_size);
|
||||
CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1));
|
||||
/* This should submit WINDOW_UPDATE frame because stream-level
|
||||
receiving window is now full. */
|
||||
CU_ASSERT(0 ==
|
||||
nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 1,
|
||||
NGHTTP2_INITIAL_WINDOW_SIZE));
|
||||
CU_ASSERT(0 == stream->recv_window_size);
|
||||
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
|
||||
nghttp2_session_get_stream_local_window_size(session, 1));
|
||||
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
|
||||
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
|
||||
CU_ASSERT(1 == item->frame.hd.stream_id);
|
||||
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
|
||||
item->frame.window_update.window_size_increment);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
|
||||
/* Make sure that nghttp2_session_set_local_window_size submits
|
||||
WINDOW_UPDATE if necessary to increase connection-level
|
||||
window. */
|
||||
nghttp2_session_client_new(&session, &callbacks, NULL);
|
||||
session->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_set_local_window_size(
|
||||
session, NGHTTP2_FLAG_NONE, 0, 0));
|
||||
CU_ASSERT(0 == session->recv_window_size);
|
||||
CU_ASSERT(0 == nghttp2_session_get_local_window_size(session));
|
||||
/* This should submit WINDOW_UPDATE frame because connection-level
|
||||
receiving window is now full. */
|
||||
CU_ASSERT(0 ==
|
||||
nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0,
|
||||
NGHTTP2_INITIAL_WINDOW_SIZE));
|
||||
CU_ASSERT(0 == session->recv_window_size);
|
||||
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
|
||||
nghttp2_session_get_local_window_size(session));
|
||||
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
|
||||
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
|
||||
CU_ASSERT(0 == item->frame.hd.stream_id);
|
||||
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
|
||||
item->frame.window_update.window_size_increment);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_cancel_from_before_frame_send(void) {
|
||||
|
|
Loading…
Reference in New Issue