Merge branch 'window_size_control' of https://github.com/akamai/nghttp2 into akamai-window_size_control

This commit is contained in:
Tatsuhiro Tsujikawa 2014-08-25 21:09:26 +09:00
commit 3655090997
7 changed files with 202 additions and 28 deletions

View File

@ -598,7 +598,7 @@ typedef struct {
*/ */
int32_t stream_id; int32_t stream_id;
/** /**
* The type of this frame. See `nghttp2_frame`. * The type of this frame. See `nghttp2_frame_type`.
*/ */
uint8_t type; uint8_t type;
/** /**
@ -642,6 +642,30 @@ typedef enum {
NGHTTP2_DATA_FLAG_EOF = 0x01 NGHTTP2_DATA_FLAG_EOF = 0x01
} nghttp2_data_flag; } nghttp2_data_flag;
/**
* @functypedef
*
* Callback function invoked when |session| wants to get max |length|
* of data to send data to the remote peer. The implementation of this
* function should return a value in the following range.
* [1, min(session window, stream window, settings remote max frame size)].
* If a window size greater than this range is returned than the max allow
* value will be used. Returning a window size smaller than this range is
* a callback error. The frame_type is provided for future extensibility
* and identifies the type of frame (see nghttp2_frame_type) for which to
* get the |length| for. Currently supported frame types are: NGHTTP2_DATA.
*
* This callback can be used to control the |length| in bytes
* for which `nghttp2_data_source_read_callback()` is allowed to send to the
* remote endpoint. This callback is optional.
* Returning :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` will signal the entire session
* failure.
*/
typedef ssize_t (*nghttp2_data_source_read_length_callback)
(nghttp2_session *session, int32_t stream_id, int32_t session_remote_window_size,
int32_t stream_remote_window_size, uint32_t remote_max_frame_size, uint8_t frame_type,
void *user_data);
/** /**
* @functypedef * @functypedef
* *
@ -1475,6 +1499,11 @@ typedef struct {
* frame. * frame.
*/ */
nghttp2_select_padding_callback select_padding_callback; nghttp2_select_padding_callback select_padding_callback;
/**
* The callback function used to determine the |length| allowed
* in `nghttp2_data_source_read_callback()`
*/
nghttp2_data_source_read_length_callback read_length_callback;
} nghttp2_session_callbacks; } nghttp2_session_callbacks;
struct nghttp2_option; struct nghttp2_option;
@ -1985,7 +2014,6 @@ int32_t nghttp2_session_get_effective_local_window_size
int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session* session, int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session* session,
int32_t stream_id); int32_t stream_id);
/** /**
* @function * @function
* *

View File

@ -55,8 +55,9 @@
/* Number of inbound buffer */ /* Number of inbound buffer */
#define NGHTTP2_FRAMEBUF_MAX_NUM 5 #define NGHTTP2_FRAMEBUF_MAX_NUM 5
/* The maximum length of DATA frame payload. */ /* The default length of DATA frame payload. This should be small enough
#define NGHTTP2_DATA_PAYLOADLEN 4096 * for the data payload and the header to fit into 1 TLS record */
#define NGHTTP2_DATA_PAYLOADLEN ((NGHTTP2_MAX_FRAME_SIZE_MIN) - (NGHTTP2_FRAME_HDLEN))
/* Maximum headers payload length, calculated in compressed form. /* Maximum headers payload length, calculated in compressed form.
This applies to transmission only. */ This applies to transmission only. */
@ -79,6 +80,9 @@
NGHTTP2_ALTSVC_FIXED_PARTLEN + Host-Len. */ NGHTTP2_ALTSVC_FIXED_PARTLEN + Host-Len. */
#define NGHTTP2_ALTSVC_MINLEN 8 #define NGHTTP2_ALTSVC_MINLEN 8
/* Maximum length of padding in bytes. */
#define NGHTTP2_MAX_PADLEN 256
/* Category of frames. */ /* Category of frames. */
typedef enum { typedef enum {
/* non-DATA frame */ /* non-DATA frame */

View File

@ -1323,33 +1323,41 @@ static int nghttp2_session_predicate_settings_send(nghttp2_session *session,
return 0; return 0;
} }
/* /* Take into account settings max frame size and both connection-level flow control here */
* Returns the maximum length of next data read. If the static ssize_t nghttp2_session_enforce_flow_control_limits(nghttp2_session *session,
* connection-level and/or stream-wise flow control are enabled, the nghttp2_stream *stream,
* return value takes into account those current window sizes. ssize_t requested_window_size)
*/
static size_t nghttp2_session_next_data_read(nghttp2_session *session,
nghttp2_stream *stream)
{ {
int32_t window_size = NGHTTP2_DATA_PAYLOADLEN;
DEBUGF(fprintf(stderr, DEBUGF(fprintf(stderr,
"send: remote windowsize connection=%d, " "send: remote windowsize connection=%d, remote maxframsize=%u, "
"stream(id %d)=%d\n", "stream(id %d)=%d\n",
session->remote_window_size, session->remote_window_size,
session->remote_settings.max_frame_size,
stream->stream_id, stream->stream_id,
stream->remote_window_size)); stream->remote_window_size));
/* Take into account both connection-level flow control here */ return nghttp2_min(
window_size = nghttp2_min(window_size, session->remote_window_size); nghttp2_min(nghttp2_min(requested_window_size, stream->remote_window_size),
window_size = nghttp2_min(window_size, stream->remote_window_size); session->remote_window_size),
session->remote_settings.max_frame_size);
}
DEBUGF(fprintf(stderr, "send: available window=%d\n", window_size)); /*
* Returns the maximum length of next data read. If the
* connection-level and/or stream-wise flow control are enabled, the
* return value takes into account those current window sizes. The remote
* settings for max frame size is also taken into account.
*/
static size_t nghttp2_session_next_data_read(nghttp2_session *session,
nghttp2_stream *stream)
{
ssize_t window_size = nghttp2_session_enforce_flow_control_limits(session,
stream,
NGHTTP2_DATA_PAYLOADLEN);
if(window_size > 0) { DEBUGF(fprintf(stderr, "send: available window=%zd\n", window_size));
return window_size;
} return window_size > 0 ? (size_t) window_size : 0;
return 0;
} }
/* /*
@ -1414,8 +1422,7 @@ static ssize_t session_call_select_padding(nghttp2_session *session,
if(session->callbacks.select_padding_callback) { if(session->callbacks.select_padding_callback) {
size_t max_paddedlen; size_t max_paddedlen;
/* 256 is maximum padding size */ max_paddedlen = nghttp2_min(frame->hd.length + NGHTTP2_MAX_PADLEN, max_payloadlen);
max_paddedlen = nghttp2_min(frame->hd.length + 256, max_payloadlen);
rv = session->callbacks.select_padding_callback(session, frame, rv = session->callbacks.select_padding_callback(session, frame,
max_paddedlen, max_paddedlen,
@ -1444,7 +1451,7 @@ static int session_headers_add_pad(nghttp2_session *session,
aob = &session->aob; aob = &session->aob;
framebufs = &aob->framebufs; framebufs = &aob->framebufs;
max_payloadlen = nghttp2_min(NGHTTP2_MAX_PAYLOADLEN, frame->hd.length + 256); max_payloadlen = nghttp2_min(NGHTTP2_MAX_PAYLOADLEN, frame->hd.length + NGHTTP2_MAX_PADLEN);
padded_payloadlen = session_call_select_padding(session, frame, padded_payloadlen = session_call_select_padding(session, frame,
max_payloadlen); max_payloadlen);
@ -5563,6 +5570,36 @@ int nghttp2_session_pack_data(nghttp2_session *session,
buf = &bufs->cur->buf; buf = &bufs->cur->buf;
if(session->callbacks.read_length_callback) {
nghttp2_stream *stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(!stream) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
payloadlen = session->callbacks.read_length_callback(session, stream->stream_id,
session->remote_window_size, stream->remote_window_size,
session->remote_settings.max_frame_size, frame->hd.type, session->user_data);
DEBUGF(fprintf(stderr, "send: read_length_callback=%zd\n", payloadlen));
payloadlen = nghttp2_session_enforce_flow_control_limits(session, stream, payloadlen);
DEBUGF(fprintf(stderr, "send: read_length_callback after flow control=%zd\n", payloadlen));
if(payloadlen <= 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
} else if(payloadlen > nghttp2_buf_avail(buf)) {
// Resize the current buffer(s)
nghttp2_bufs_free(&session->aob.framebufs);
// The reason why we do +1 for buffer size is for possible padding field.
rv = nghttp2_bufs_init3(&session->aob.framebufs,
NGHTTP2_FRAME_HDLEN + 1 + payloadlen,
NGHTTP2_FRAMEBUF_MAX_NUM,
1, NGHTTP2_FRAME_HDLEN + 1);
if(rv != 0) {
return rv;
}
}
datamax = (size_t) payloadlen;
}
/* Current max DATA length is less then buffer chunk size */ /* Current max DATA length is less then buffer chunk size */
assert(nghttp2_buf_avail(buf) >= (ssize_t)datamax); assert(nghttp2_buf_avail(buf) >= (ssize_t)datamax);
@ -5606,7 +5643,7 @@ int nghttp2_session_pack_data(nghttp2_session *session,
data_frame.hd.flags = flags; data_frame.hd.flags = flags;
data_frame.data.padlen = 0; data_frame.data.padlen = 0;
max_payloadlen = nghttp2_min(datamax, data_frame.hd.length + 256); max_payloadlen = nghttp2_min(datamax, data_frame.hd.length + NGHTTP2_MAX_PADLEN);
padded_payloadlen = session_call_select_padding(session, &data_frame, padded_payloadlen = session_call_select_padding(session, &data_frame,
max_payloadlen); max_payloadlen);

View File

@ -333,7 +333,7 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
int32_t window_size_increment) int32_t window_size_increment)
{ {
int rv; int rv;
nghttp2_stream *stream; nghttp2_stream *stream = 0;
if(window_size_increment == 0) { if(window_size_increment == 0) {
return 0; return 0;
} }

View File

@ -142,6 +142,8 @@ int main(int argc, char* argv[])
!CU_add_test(pSuite, "session_reprioritize_stream", !CU_add_test(pSuite, "session_reprioritize_stream",
test_nghttp2_session_reprioritize_stream) || test_nghttp2_session_reprioritize_stream) ||
!CU_add_test(pSuite, "submit_data", test_nghttp2_submit_data) || !CU_add_test(pSuite, "submit_data", test_nghttp2_submit_data) ||
!CU_add_test(pSuite, "submit_data_read_length_too_large",
test_nghttp2_submit_data_read_length_too_large) ||
!CU_add_test(pSuite, "submit_request_with_data", !CU_add_test(pSuite, "submit_request_with_data",
test_nghttp2_submit_request_with_data) || test_nghttp2_submit_request_with_data) ||
!CU_add_test(pSuite, "submit_request_without_data", !CU_add_test(pSuite, "submit_request_without_data",
@ -239,6 +241,8 @@ int main(int argc, char* argv[])
test_nghttp2_frame_pack_headers) || test_nghttp2_frame_pack_headers) ||
!CU_add_test(pSuite, "frame_pack_headers_frame_too_large", !CU_add_test(pSuite, "frame_pack_headers_frame_too_large",
test_nghttp2_frame_pack_headers_frame_too_large) || test_nghttp2_frame_pack_headers_frame_too_large) ||
!CU_add_test(pSuite, "frame_pack_headers_frame_smallest",
test_nghttp2_submit_data_read_length_smallest) ||
!CU_add_test(pSuite, "frame_pack_priority", !CU_add_test(pSuite, "frame_pack_priority",
test_nghttp2_frame_pack_priority) || test_nghttp2_frame_pack_priority) ||
!CU_add_test(pSuite, "frame_pack_rst_stream", !CU_add_test(pSuite, "frame_pack_rst_stream",

View File

@ -228,6 +228,20 @@ static ssize_t select_padding_callback(nghttp2_session *session,
return nghttp2_min(max_payloadlen, frame->hd.length + ud->padlen); return nghttp2_min(max_payloadlen, frame->hd.length + ud->padlen);
} }
static ssize_t too_large_data_source_length_callback
(nghttp2_session *session, int32_t stream_id,
int32_t session_remote_window_size, int32_t stream_remote_window_size,
uint32_t remote_max_frame_size, uint8_t frame_type, void *user_data) {
return NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
}
static ssize_t smallest_length_data_source_length_callback
(nghttp2_session *session, int32_t stream_id,
int32_t session_remote_window_size, int32_t stream_remote_window_size,
uint32_t remote_max_frame_size, uint8_t frame_type, void *user_data) {
return 1;
}
static ssize_t fixed_length_data_source_read_callback static ssize_t fixed_length_data_source_read_callback
(nghttp2_session *session, int32_t stream_id, (nghttp2_session *session, int32_t stream_id,
uint8_t *buf, size_t len, uint32_t *data_flags, uint8_t *buf, size_t len, uint32_t *data_flags,
@ -3018,6 +3032,92 @@ void test_nghttp2_submit_data(void)
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_submit_data_read_length_too_large(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_data_provider data_prd;
my_user_data ud;
nghttp2_private_data *data_frame;
nghttp2_frame_hd hd;
nghttp2_active_outbound_item *aob;
nghttp2_bufs *framebufs;
nghttp2_buf *buf;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = block_count_send_callback;
callbacks.read_length_callback = too_large_data_source_length_callback;
data_prd.read_callback = fixed_length_data_source_read_callback;
ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
aob = &session->aob;
framebufs = &aob->framebufs;
nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default, NGHTTP2_STREAM_OPENING,
NULL);
CU_ASSERT(0 == nghttp2_submit_data(session,
NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
ud.block_count = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
data_frame = nghttp2_outbound_item_get_data_frame(aob->item);
buf = &framebufs->head->buf;
nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
CU_ASSERT(16384 == hd.length)
/* frame->hd.flags has these flags */
CU_ASSERT(NGHTTP2_FLAG_END_STREAM == data_frame->hd.flags);
nghttp2_session_del(session);
}
void test_nghttp2_submit_data_read_length_smallest(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_data_provider data_prd;
my_user_data ud;
nghttp2_private_data *data_frame;
nghttp2_frame_hd hd;
nghttp2_active_outbound_item *aob;
nghttp2_bufs *framebufs;
nghttp2_buf *buf;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = block_count_send_callback;
callbacks.read_length_callback = smallest_length_data_source_length_callback;
data_prd.read_callback = fixed_length_data_source_read_callback;
ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
aob = &session->aob;
framebufs = &aob->framebufs;
nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default, NGHTTP2_STREAM_OPENING,
NULL);
CU_ASSERT(0 == nghttp2_submit_data(session,
NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
ud.block_count = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
data_frame = nghttp2_outbound_item_get_data_frame(aob->item);
buf = &framebufs->head->buf;
nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
CU_ASSERT(1 == hd.length)
/* frame->hd.flags has these flags */
CU_ASSERT(NGHTTP2_FLAG_END_STREAM == data_frame->hd.flags);
nghttp2_session_del(session);
}
void test_nghttp2_submit_request_with_data(void) void test_nghttp2_submit_request_with_data(void)
{ {
nghttp2_session *session; nghttp2_session *session;
@ -4316,7 +4416,6 @@ void test_nghttp2_session_defer_data(void)
CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2);
/* Resume deferred DATA */ /* Resume deferred DATA */
CU_ASSERT(0 == nghttp2_session_resume_data(session, 1)); CU_ASSERT(0 == nghttp2_session_resume_data(session, 1));
item = nghttp2_session_get_ob_pq_top(session); item = nghttp2_session_get_ob_pq_top(session);
OB_DATA(item)->data_prd.read_callback = OB_DATA(item)->data_prd.read_callback =

View File

@ -62,6 +62,8 @@ void test_nghttp2_session_is_my_stream_id(void);
void test_nghttp2_session_upgrade(void); void test_nghttp2_session_upgrade(void);
void test_nghttp2_session_reprioritize_stream(void); void test_nghttp2_session_reprioritize_stream(void);
void test_nghttp2_submit_data(void); void test_nghttp2_submit_data(void);
void test_nghttp2_submit_data_read_length_too_large(void);
void test_nghttp2_submit_data_read_length_smallest(void);
void test_nghttp2_submit_request_with_data(void); void test_nghttp2_submit_request_with_data(void);
void test_nghttp2_submit_request_without_data(void); void test_nghttp2_submit_request_without_data(void);
void test_nghttp2_submit_response_with_data(void); void test_nghttp2_submit_response_with_data(void);