Fix handling of SETTINGS_MAX_CONCURRENT_STREAMS.

The maximum number of outgoing concurrent streams is initially
limited to 100 to avoid issues when the local endpoint submits
lots of requests before receiving initial SETTINGS frame from
the remote endpoint, since sending them at once to the remote
endpoint could lead to rejection of some of the requests.

This initial limit is overwritten with the value advertised in
SETTINGS_MAX_CONCURRENT_STREAMS setting by the remote endpoint,
but previously, it wasn't lifted if the remote endpoint didn't
advertise that setting (implying no limits), in which case the
limit of 100 was retained, even though it was never advertised
by the remote endpoint.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
This commit is contained in:
Piotr Sikora 2018-05-30 20:24:00 -07:00
parent 575bc309b9
commit 2ba1389993
4 changed files with 37 additions and 11 deletions

View File

@ -2478,15 +2478,15 @@ nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val);
* *
* This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of
* remote endpoint as if it is received in SETTINGS frame. Without * remote endpoint as if it is received in SETTINGS frame. Without
* specifying this option, before the local endpoint receives * specifying this option, the maximum number of outgoing concurrent
* SETTINGS_MAX_CONCURRENT_STREAMS in SETTINGS frame from remote * streams is initially limited to 100 to avoid issues when the local
* endpoint, SETTINGS_MAX_CONCURRENT_STREAMS is unlimited. This may * endpoint submits lots of requests before receiving initial SETTINGS
* cause problem if local endpoint submits lots of requests initially * frame from the remote endpoint, since sending them at once to the
* and sending them at once to the remote peer may lead to the * remote endpoint could lead to rejection of some of the requests.
* rejection of some requests. Specifying this option to the sensible * This value will be overwritten when the local endpoint receives
* value, say 100, may avoid this kind of issue. This value will be * initial SETTINGS frame from the remote endpoint, either to the
* overwritten if the local endpoint receives * value advertised in SETTINGS_MAX_CONCURRENT_STREAMS or to the
* SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint. * default value (unlimited) if none was advertised.
*/ */
NGHTTP2_EXTERN void NGHTTP2_EXTERN void
nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,

View File

@ -4410,6 +4410,12 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
return session_call_on_frame_received(session, frame); return session_call_on_frame_received(session, frame);
} }
if (!session->remote_settings_received) {
session->remote_settings.max_concurrent_streams =
NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS;
session->remote_settings_received = 1;
}
for (i = 0; i < frame->settings.niv; ++i) { for (i = 0; i < frame->settings.niv; ++i) {
nghttp2_settings_entry *entry = &frame->settings.iv[i]; nghttp2_settings_entry *entry = &frame->settings.iv[i];

View File

@ -303,8 +303,10 @@ struct nghttp2_session {
increased/decreased by submitting WINDOW_UPDATE. See increased/decreased by submitting WINDOW_UPDATE. See
nghttp2_submit_window_update(). */ nghttp2_submit_window_update(). */
int32_t local_window_size; int32_t local_window_size;
/* Settings value received from the remote endpoint. We just use ID /* This flag is used to indicate that the local endpoint received initial
as index. The index = 0 is unused. */ SETTINGS frame from the remote endpoint. */
uint8_t remote_settings_received;
/* Settings value received from the remote endpoint. */
nghttp2_settings_storage remote_settings; nghttp2_settings_storage remote_settings;
/* Settings value of the local endpoint. */ /* Settings value of the local endpoint. */
nghttp2_settings_storage local_settings; nghttp2_settings_storage local_settings;

View File

@ -3294,6 +3294,7 @@ void test_nghttp2_session_on_settings_received(void) {
nghttp2_outbound_item *item; nghttp2_outbound_item *item;
nghttp2_nv nv = MAKE_NV(":authority", "example.org"); nghttp2_nv nv = MAKE_NV(":authority", "example.org");
nghttp2_mem *mem; nghttp2_mem *mem;
nghttp2_option *option;
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
@ -3406,6 +3407,23 @@ void test_nghttp2_session_on_settings_received(void) {
nghttp2_frame_settings_free(&frame.settings, mem); nghttp2_frame_settings_free(&frame.settings, mem);
nghttp2_session_del(session); nghttp2_session_del(session);
/* Check that remote SETTINGS_MAX_CONCURRENT_STREAMS is set to a value set by
nghttp2_option_set_peer_max_concurrent_streams() and reset to the default
value (unlimited) after receiving initial SETTINGS frame from the peer. */
nghttp2_option_new(&option);
nghttp2_option_set_peer_max_concurrent_streams(option, 1000);
nghttp2_session_client_new2(&session, &callbacks, NULL, option);
CU_ASSERT(1000 == session->remote_settings.max_concurrent_streams);
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS ==
session->remote_settings.max_concurrent_streams);
nghttp2_frame_settings_free(&frame.settings, mem);
nghttp2_session_del(session);
nghttp2_option_del(option);
/* Check too large SETTINGS_MAX_FRAME_SIZE */ /* Check too large SETTINGS_MAX_FRAME_SIZE */
nghttp2_session_server_new(&session, &callbacks, NULL); nghttp2_session_server_new(&session, &callbacks, NULL);