Add nghttp2_session_consume() API

Reworked no automatic WINDOW_UPDATE feature.  We added new API
nghttp2_session_consume() which tells the library how many bytes are
consumed by the application.  Instead of submitting WINDOW_UPDATE by
the application, the library is now responsible to submit
WINDOW_UPDATE based on consumed bytes.  This is more reliable method,
since it enables us to properly send WINDOW_UPDATE for stream and
connection individually.  The previous implementation of nghttpx had
broken connection window management.
This commit is contained in:
Tatsuhiro Tsujikawa
2014-07-25 21:26:03 +09:00
parent 9f17bee51d
commit 079db14d45
18 changed files with 395 additions and 276 deletions

View File

@@ -1505,31 +1505,17 @@ int nghttp2_option_new(nghttp2_option **option_ptr);
*/
void nghttp2_option_del(nghttp2_option *option);
/**
* @function
*
* This option prevents the library from sending WINDOW_UPDATE for a
* stream automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a stream and the application
* is responsible for sending WINDOW_UPDATE using
* `nghttp2_submit_window_update`. By default, this option is set to
* zero.
*/
void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option,
int val);
/**
* @function
*
* This option prevents the library from sending WINDOW_UPDATE for a
* connection automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a connection and the
* application is responsible for sending WINDOW_UPDATE with stream ID
* 0 using `nghttp2_submit_window_update`. By default, this option is
* set to zero.
* library won't send WINDOW_UPDATE for DATA until application calls
* `nghttp2_session_consume()` to indicate the consumed amount of
* data. Don't use `nghttp2_submit_window_update()` for this purpose.
* By default, this option is set to zero.
*/
void nghttp2_option_set_no_auto_connection_window_update
(nghttp2_option *option, int val);
void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val);
/**
* @function
@@ -2067,6 +2053,28 @@ int nghttp2_session_terminate_session2(nghttp2_session *session,
uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
nghttp2_settings_id id);
/**
* @function
*
* Tells the |session| that |size| bytes for a stream denoted by
* |stream_id| were consumed by application and are ready to
* WINDOW_UPDATE. This function is intended to be used without
* automatic window update (see
* `nghttp2_option_set_no_auto_window_update()`).
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |stream_id| is 0.
* :enum:`NGHTTP2_ERR_INVALID_STATE`
* Automatic WINDOW_UPDATE is not disabled.
*/
int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id,
size_t size);
/**
* @function
*
@@ -2609,12 +2617,11 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags,
* difference.
*
* If the |window_size_increment| is negative, the local window size
* is decreased by -|window_size_increment|. If
* :enum:`NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE` (or
* :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE` if |stream_id|
* is 0) is not set and the library decided that the WINDOW_UPDATE
* should be submitted, then WINDOW_UPDATE is queued with the current
* received bytes count.
* is decreased by -|window_size_increment|. If automatic
* WINDOW_UPDATE is enabled
* (`nghttp2_option_set_no_auto_window_update()`), and the library
* decided that the WINDOW_UPDATE should be submitted, then
* WINDOW_UPDATE is queued with the current received bytes count.
*
* If the |window_size_increment| is 0, the function does nothing and
* returns 0.

View File

@@ -40,18 +40,10 @@ void nghttp2_option_del(nghttp2_option *option)
free(option);
}
void nghttp2_option_set_no_auto_stream_window_update(nghttp2_option *option,
int val)
void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val)
{
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE;
option->no_auto_stream_window_update = val;
}
void nghttp2_option_set_no_auto_connection_window_update
(nghttp2_option *option, int val)
{
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE;
option->no_auto_connection_window_update = val;
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE;
option->no_auto_window_update = val;
}
void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,

View File

@@ -37,22 +37,12 @@
typedef enum {
/**
* This option prevents the library from sending WINDOW_UPDATE for a
* stream automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a stream and the application
* is responsible for sending WINDOW_UPDATE using
* `nghttp2_submit_window_update`. By default, this option is set to
* zero.
* connection automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for DATA until application calls
* nghttp2_session_consume() to indicate the amount of consumed
* DATA. By default, this option is set to zero.
*/
NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE = 1,
/**
* This option prevents the library from sending WINDOW_UPDATE for a
* connection automatically. If this option is set to nonzero, the
* library won't send WINDOW_UPDATE for a connection and the
* application is responsible for sending WINDOW_UPDATE with stream
* ID 0 using `nghttp2_submit_window_update`. By default, this
* option is set to zero.
*/
NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1,
NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE = 1,
/**
* This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of
* remote endpoint as if it is received in SETTINGS frame. Without
@@ -66,7 +56,7 @@ typedef enum {
* will be overwritten if the local endpoint receives
* SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint.
*/
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 2
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1
} nghttp2_option_flag;
/**
@@ -83,13 +73,9 @@ struct nghttp2_option {
*/
uint32_t peer_max_concurrent_streams;
/**
* NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE
* NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE
*/
uint8_t no_auto_stream_window_update;
/**
* NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE
*/
uint8_t no_auto_connection_window_update;
uint8_t no_auto_window_update;
};
#endif /* NGHTTP2_OPTION_H */

View File

@@ -352,6 +352,7 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->recv_window_size = 0;
(*session_ptr)->consumed_size = 0;
(*session_ptr)->recv_reduction = 0;
(*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
@@ -383,19 +384,10 @@ static int session_new(nghttp2_session **session_ptr,
if(option) {
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE) &&
option->no_auto_stream_window_update) {
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
option->no_auto_window_update) {
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE;
}
if((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE) &&
option->no_auto_connection_window_update) {
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE;
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE;
}
@@ -3175,10 +3167,11 @@ static int update_local_initial_window_size_func
return nghttp2_session_terminate_session(arg->session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
if(!(arg->session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) {
if(!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(stream->local_window_size,
stream->recv_window_size)) {
rv = nghttp2_session_add_window_update(arg->session,
NGHTTP2_FLAG_NONE,
stream->stream_id,
@@ -3876,9 +3869,9 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr,
/*
* 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_STREAM_WINDOW_UPDATE is set,
* WINDOW_UPDATE will not be sent.
* 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:
@@ -3902,7 +3895,7 @@ static int session_update_recv_stream_window_size
/* We don't have to send WINDOW_UPDATE if the data received is the
last chunk in the incoming stream. */
if(send_window_update &&
!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) {
!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
/* We have to use local_settings here because it is the constraint
the remote endpoint should honor. */
if(nghttp2_should_send_window_update(stream->local_window_size,
@@ -3924,8 +3917,8 @@ static int session_update_recv_stream_window_size
/*
* 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_CONNECTION_WINDOW_UPDATE is
* set, WINDOW_UPDATE will not be sent.
* 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:
@@ -3944,30 +3937,92 @@ static int session_update_recv_connection_window_size
return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
if(!(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE)) {
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(session->local_window_size,
session->recv_window_size)) {
/* Use stream ID 0 to update connection-level flow control
window */
rv = nghttp2_session_add_window_update(session,
NGHTTP2_FLAG_NONE,
0,
session->recv_window_size);
if(rv == 0) {
session->recv_window_size = 0;
/* recv_ign_window_size keeps track of ignored DATA bytes
before any connection-level WINDOW_UPDATE therefore, we can
reset it here. */
session->recv_ign_window_size = 0;
} else {
rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0,
session->recv_window_size);
if(rv != 0) {
return rv;
}
session->recv_window_size = 0;
}
}
return 0;
}
static int session_update_stream_consumed_size
(nghttp2_session *session, nghttp2_stream *stream, size_t delta_size)
{
int32_t recv_size;
int rv;
if((size_t)stream->consumed_size > NGHTTP2_MAX_WINDOW_SIZE - delta_size) {
return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
stream->consumed_size += delta_size;
/* recv_window_size may be smaller than consumed_size, because it
may be decreased by negative value with
nghttp2_submit_window_update(). */
recv_size = nghttp2_min(stream->consumed_size, stream->recv_window_size);
if(nghttp2_should_send_window_update(stream->local_window_size, recv_size)) {
rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE,
stream->stream_id, recv_size);
if(rv != 0) {
return rv;
}
stream->recv_window_size -= recv_size;
stream->consumed_size -= recv_size;
}
return 0;
}
static int session_update_connection_consumed_size
(nghttp2_session *session, size_t delta_size)
{
int32_t recv_size;
int rv;
if((size_t)session->consumed_size > NGHTTP2_MAX_WINDOW_SIZE - delta_size) {
return nghttp2_session_terminate_session(session,
NGHTTP2_FLOW_CONTROL_ERROR);
}
session->consumed_size += delta_size;
/* recv_window_size may be smaller than consumed_size, because it
may be decreased by negative value with
nghttp2_submit_window_update(). */
recv_size = nghttp2_min(session->consumed_size, session->recv_window_size);
if(nghttp2_should_send_window_update(session->local_window_size,
recv_size)) {
rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0,
recv_size);
if(rv != 0) {
return rv;
}
session->recv_window_size -= recv_size;
session->consumed_size -= recv_size;
}
return 0;
}
/*
* Checks that we can receive the DATA frame for stream, which is
* indicated by |session->iframe.frame.hd.stream_id|. If it is a
@@ -5033,6 +5088,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
return rv;
}
/* Pad Length field is consumed immediately */
rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id,
readlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
stream = nghttp2_session_get_stream(session,
iframe->frame.hd.stream_id);
if(stream) {
@@ -5098,6 +5161,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
data_readlen = inbound_frame_effective_readlen
(iframe, iframe->payloadleft, readlen);
padlen = readlen - data_readlen;
if(padlen > 0) {
/* Padding is considered as "consumed" immediately */
rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id,
padlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
}
DEBUGF(fprintf(stderr, "recv: data_readlen=%zu\n", data_readlen));
if(stream && data_readlen > 0 &&
@@ -5142,8 +5217,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
readlen, iframe->payloadleft));
if(readlen > 0) {
session->recv_ign_window_size += readlen;
/* Update connection-level flow control window for ignored
DATA frame too */
rv = session_update_recv_connection_window_size(session, readlen);
@@ -5151,20 +5224,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
return rv;
}
if((session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE) &&
nghttp2_should_send_window_update
(session->local_window_size, session->recv_ign_window_size)) {
if(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
rv = nghttp2_session_add_window_update
(session, NGHTTP2_FLAG_NONE, 0, session->recv_ign_window_size);
/* Ignored DATA is considered as "consumed" immediately. */
rv = session_update_connection_consumed_size(session, readlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
session->recv_window_size -= session->recv_ign_window_size;
session->recv_ign_window_size = 0;
}
}
@@ -5737,3 +5804,36 @@ int nghttp2_session_get_stream_remote_close(nghttp2_session* session,
return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0;
}
int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id,
size_t size)
{
int rv;
nghttp2_stream *stream;
if(stream_id == 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
return NGHTTP2_ERR_INVALID_STATE;
}
rv = session_update_connection_consumed_size(session, size);
if(nghttp2_is_fatal(rv)) {
return rv;
}
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
rv = session_update_stream_consumed_size(session, stream, size);
if(nghttp2_is_fatal(rv)) {
return rv;
}
}
return 0;
}

View File

@@ -43,8 +43,7 @@
* Option flags.
*/
typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE = 1 << 0,
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1
NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
} nghttp2_optmask;
typedef enum {
@@ -206,15 +205,10 @@ struct nghttp2_session {
WINDOW_UPDATE. This could be negative after submitting negative
value to WINDOW_UPDATE. */
int32_t recv_window_size;
/* The number of bytes in ignored DATA frame received without
connection-level WINDOW_UPDATE. Since we do not call
on_data_chunk_recv_callback for ignored DATA chunk, if
nghttp2_option_set_no_auto_connection_window_update is used,
application may not have a chance to send connection
WINDOW_UPDATE. To fix this, we accumulate those received bytes,
and if it exceeds certain number, we automatically send
connection-level WINDOW_UPDATE. */
int32_t recv_ign_window_size;
/* The number of bytes consumed by the application and now is
subject to WINDOW_UPDATE. This is only used when auto
WINDOW_UPDATE is turned off. */
int32_t consumed_size;
/* The amount of recv_window_size cut using submitting negative
value to WINDOW_UPDATE */
int32_t recv_reduction;

View File

@@ -48,6 +48,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
stream->remote_window_size = remote_initial_window_size;
stream->local_window_size = local_initial_window_size;
stream->recv_window_size = 0;
stream->consumed_size = 0;
stream->recv_reduction = 0;
stream->blocked_sent = 0;

View File

@@ -151,6 +151,10 @@ struct nghttp2_stream {
WINDOW_UPDATE. This could be negative after submitting negative
value to WINDOW_UPDATE */
int32_t recv_window_size;
/* The number of bytes consumed by the application and now is
subject to WINDOW_UPDATE. This is only used when auto
WINDOW_UPDATE is turned off. */
int32_t consumed_size;
/* The amount of recv_window_size cut using submitting negative
value to WINDOW_UPDATE */
int32_t recv_reduction;

View File

@@ -348,27 +348,30 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
if(rv != 0) {
return rv;
}
/* recv_ign_window_size keeps track of ignored DATA bytes before
any connection-level WINDOW_UPDATE therefore, we can reset it
here. */
session->recv_ign_window_size = 0;
} else {
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
rv = nghttp2_adjust_local_window_size(&stream->local_window_size,
&stream->recv_window_size,
&stream->recv_reduction,
&window_size_increment);
if(rv != 0) {
return rv;
}
} else {
if(!stream) {
return 0;
}
rv = nghttp2_adjust_local_window_size(&stream->local_window_size,
&stream->recv_window_size,
&stream->recv_reduction,
&window_size_increment);
if(rv != 0) {
return rv;
}
}
if(window_size_increment > 0) {
if(stream_id == 0) {
session->consumed_size =
nghttp2_max(0, session->consumed_size - window_size_increment);
} else {
stream->consumed_size =
nghttp2_max(0, stream->consumed_size - window_size_increment);
}
return nghttp2_session_add_window_update(session, flags, stream_id,
window_size_increment);
}