Allow PRIORITY frame at anytime.

Allowing PRIORITY frame at anytime so that PRIORITY frame to idle
stream can create anchor node in dependency tree.  In this change, we
open stream with new NGHTTP2_STREAM_IDLE state, which is linked in
session->closed_stream_head and is treated as if it is closed stream.
One difference is that if the stream is opened, we remove it from
linked list and change the state to the appropriate one.  To O(1)
removal from linked list, we change session->closed_stream_head to
doubly linked list.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-11-08 00:36:47 +09:00
parent 9bba616426
commit ae93f6345c
7 changed files with 324 additions and 159 deletions

View File

@ -198,7 +198,8 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session,
stream = (nghttp2_stream*)nghttp2_map_find(&session->streams, stream_id); stream = (nghttp2_stream*)nghttp2_map_find(&session->streams, stream_id);
if(stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED)) { if(stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) ||
stream->state == NGHTTP2_STREAM_IDLE) {
return NULL; return NULL;
} }
@ -689,9 +690,12 @@ int nghttp2_session_add_item(nghttp2_session *session,
case NGHTTP2_RST_STREAM: case NGHTTP2_RST_STREAM:
if(stream) { if(stream) {
/* We rely on the stream state to decide whether number of /* We rely on the stream state to decide whether number of
streams should be decremented or not. For purly reserved streams should be decremented or not. For purly reserved or
streams, they are not counted to those numbers and we must idle streams, they are not counted to those numbers and we
keep this state in order not to decrement the number. */ must keep this state in order not to decrement the number.
We don't check against NGHTTP2_STREAM_IDLE because
nghttp2_session_get_stream() does not return such
stream. */
if(stream->state != NGHTTP2_STREAM_RESERVED) { if(stream->state != NGHTTP2_STREAM_RESERVED) {
stream->state = NGHTTP2_STREAM_CLOSING; stream->state = NGHTTP2_STREAM_CLOSING;
} }
@ -800,10 +804,21 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
nghttp2_stream *stream; nghttp2_stream *stream;
nghttp2_stream *dep_stream = NULL; nghttp2_stream *dep_stream = NULL;
nghttp2_stream *root_stream; nghttp2_stream *root_stream;
int stream_alloc = 0;
nghttp2_priority_spec pri_spec_default; nghttp2_priority_spec pri_spec_default;
nghttp2_priority_spec *pri_spec = pri_spec_in; nghttp2_priority_spec *pri_spec = pri_spec_in;
if(session->server && !nghttp2_session_is_my_stream_id(session, stream_id)) { stream = nghttp2_session_get_stream_raw(session, stream_id);
if(stream) {
assert(stream->state == NGHTTP2_STREAM_IDLE);
assert(nghttp2_stream_in_dep_tree(stream));
nghttp2_session_detach_closed_stream(session, stream);
nghttp2_stream_dep_remove(stream);
} else {
if(session->server &&
(!nghttp2_session_is_my_stream_id(session, stream_id) ||
initial_state == NGHTTP2_STREAM_IDLE)) {
nghttp2_session_adjust_closed_stream(session, 1); nghttp2_session_adjust_closed_stream(session, 1);
} }
@ -812,6 +827,20 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
return NULL; return NULL;
} }
stream_alloc = 1;
}
if(pri_spec->stream_id != 0) {
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
/* If dep_stream is not part of dependency tree, stream will get
default priority. */
if(!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) {
nghttp2_priority_spec_default_init(&pri_spec_default);
pri_spec = &pri_spec_default;
}
}
if(pri_spec->stream_id != 0) { if(pri_spec->stream_id != 0) {
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
@ -829,13 +858,16 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
session->local_settings.initial_window_size, session->local_settings.initial_window_size,
stream_user_data); stream_user_data);
if(stream_alloc) {
rv = nghttp2_map_insert(&session->streams, &stream->map_entry); rv = nghttp2_map_insert(&session->streams, &stream->map_entry);
if(rv != 0) { if(rv != 0) {
free(stream); free(stream);
return NULL; return NULL;
} }
}
if(initial_state == NGHTTP2_STREAM_RESERVED) { switch(initial_state) {
case NGHTTP2_STREAM_RESERVED:
if(nghttp2_session_is_my_stream_id(session, stream_id)) { if(nghttp2_session_is_my_stream_id(session, stream_id)) {
/* half closed (remote) */ /* half closed (remote) */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
@ -845,7 +877,14 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
} }
/* Reserved stream does not count in the concurrent streams /* Reserved stream does not count in the concurrent streams
limit. That is one of the DOS vector. */ limit. That is one of the DOS vector. */
} else { break;
case NGHTTP2_STREAM_IDLE:
/* Idle stream does not count toward the concurrent streams limit.
This is used as anchor node in dependency tree. */
assert(session->server);
nghttp2_session_keep_closed_stream(session, stream);
break;
default:
if(nghttp2_session_is_my_stream_id(session, stream_id)) { if(nghttp2_session_is_my_stream_id(session, stream_id)) {
++session->num_outgoing_streams; ++session->num_outgoing_streams;
} else { } else {
@ -950,7 +989,11 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
} }
} }
if(stream->state != NGHTTP2_STREAM_RESERVED) { switch(stream->state) {
case NGHTTP2_STREAM_RESERVED:
case NGHTTP2_STREAM_IDLE:
break;
default:
if(nghttp2_session_is_my_stream_id(session, stream_id)) { if(nghttp2_session_is_my_stream_id(session, stream_id)) {
--session->num_outgoing_streams; --session->num_outgoing_streams;
} else { } else {
@ -961,12 +1004,10 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
/* Closes both directions just in case they are not closed yet */ /* Closes both directions just in case they are not closed yet */
stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED; stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED;
if(session->server && if(session->server && nghttp2_stream_in_dep_tree(stream)) {
nghttp2_stream_in_dep_tree(stream) && /* On server side, retain stream at most MAX_CONCURRENT_STREAMS
!nghttp2_session_is_my_stream_id(session, stream_id)) { combined with the current active incoming streams to make
/* On server side, retain incoming stream object at most dependency tree work better. */
MAX_CONCURRENT_STREAMS combined with the current active streams
to make dependency tree work better. */
nghttp2_session_keep_closed_stream(session, stream); nghttp2_session_keep_closed_stream(session, stream);
} else { } else {
nghttp2_session_destroy_stream(session, stream); nghttp2_session_destroy_stream(session, stream);
@ -991,11 +1032,12 @@ void nghttp2_session_destroy_stream(nghttp2_session *session,
void nghttp2_session_keep_closed_stream(nghttp2_session *session, void nghttp2_session_keep_closed_stream(nghttp2_session *session,
nghttp2_stream *stream) nghttp2_stream *stream)
{ {
DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d\n", DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n",
stream, stream->stream_id)); stream, stream->stream_id, stream->state));
if(session->closed_stream_tail) { if(session->closed_stream_tail) {
session->closed_stream_tail->closed_next = stream; session->closed_stream_tail->closed_next = stream;
stream->closed_prev = session->closed_stream_tail;
} else { } else {
session->closed_stream_head = stream; session->closed_stream_head = stream;
} }
@ -1006,6 +1048,48 @@ void nghttp2_session_keep_closed_stream(nghttp2_session *session,
nghttp2_session_adjust_closed_stream(session, 0); nghttp2_session_adjust_closed_stream(session, 0);
} }
void nghttp2_session_detach_closed_stream(nghttp2_session *session,
nghttp2_stream *stream)
{
nghttp2_stream *prev_stream, *next_stream;
DEBUGF(fprintf(stderr, "stream: detach closed stream(%p)=%d, state=%d\n",
stream, stream->stream_id, stream->state));
prev_stream = stream->closed_prev;
next_stream = stream->closed_next;
if(prev_stream) {
prev_stream->closed_next = next_stream;
} else {
session->closed_stream_head = next_stream;
}
if(next_stream) {
next_stream->closed_prev = prev_stream;
} else {
session->closed_stream_tail = prev_stream;
}
stream->closed_prev = NULL;
stream->closed_next = NULL;
--session->num_closed_streams;
}
/* Returns nonzero if closed stream can not be added to linked list
now. */
static int session_closed_stream_full(nghttp2_session *session)
{
size_t num_stream_max;
num_stream_max = nghttp2_min(session->local_settings.max_concurrent_streams,
session->pending_local_max_concurrent_stream);
return (size_t)nghttp2_max(0, (ssize_t)session->num_closed_streams - 1) +
session->num_incoming_streams >= num_stream_max;
}
void nghttp2_session_adjust_closed_stream(nghttp2_session *session, void nghttp2_session_adjust_closed_stream(nghttp2_session *session,
ssize_t offset) ssize_t offset)
{ {
@ -1029,7 +1113,9 @@ void nghttp2_session_adjust_closed_stream(nghttp2_session *session,
session->closed_stream_head = head_stream->closed_next; session->closed_stream_head = head_stream->closed_next;
if(session->closed_stream_tail == head_stream) { if(session->closed_stream_head) {
session->closed_stream_head->closed_prev = NULL;
} else {
session->closed_stream_tail = NULL; session->closed_stream_tail = NULL;
} }
@ -1233,28 +1319,6 @@ static int session_predicate_headers_send(nghttp2_session *session,
return session_predicate_stream_frame_send(session, stream_id); return session_predicate_stream_frame_send(session, stream_id);
} }
/*
* This function checks PRIORITY frame with stream ID |stream_id| can
* be sent at this time.
*/
static int session_predicate_priority_send
(nghttp2_session *session, int32_t stream_id)
{
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, stream_id);
/* PRIORITY must not be sent in reserved(local) */
if(stream) {
if(state_reserved_local(session, stream)) {
return NGHTTP2_ERR_INVALID_STREAM_STATE;
}
} else if(session_detect_idle_stream(session, stream_id)) {
return NGHTTP2_ERR_INVALID_STREAM_ID;
}
return 0;
}
/* /*
* This function checks PUSH_PROMISE frame |frame| with stream ID * This function checks PUSH_PROMISE frame |frame| with stream ID
* |stream_id| can be sent at this time. * |stream_id| can be sent at this time.
@ -1668,15 +1732,18 @@ static int session_prep_frame(nghttp2_session *session,
return framerv; return framerv;
} }
case NGHTTP2_PRIORITY: { case NGHTTP2_PRIORITY: {
rv = session_predicate_priority_send(session, frame->hd.stream_id); /* PRIORITY frame can be sent at any time and to any stream
if(rv != 0) { ID. */
return rv;
}
framerv = nghttp2_frame_pack_priority(&session->aob.framebufs, framerv = nghttp2_frame_pack_priority(&session->aob.framebufs,
&frame->priority); &frame->priority);
if(framerv < 0) { if(framerv < 0) {
return framerv; return framerv;
} }
/* Peer can send PRIORITY frame against idle stream to create
"anchor" in dependency tree. Only client can do this in
nghttp2. In nghttp2, only server retains non-active (closed
or idle) streams in memory, so we don't open stream here. */
break; break;
} }
case NGHTTP2_RST_STREAM: case NGHTTP2_RST_STREAM:
@ -2170,24 +2237,24 @@ static int session_after_frame_sent(nghttp2_session *session)
} }
case NGHTTP2_PRIORITY: { case NGHTTP2_PRIORITY: {
nghttp2_stream *stream; nghttp2_stream *stream;
if(session->server) {
break;
}
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if(!stream) { if(!stream) {
break; break;
} }
/* Only update priority of the stream, only if it is not pushed
stream and is initiated by local peer, or it is pushed stream
and is initiated by remote peer */
if(((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) == 0 &&
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) ||
((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) &&
!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id))) {
rv = nghttp2_session_reprioritize_stream(session, stream, rv = nghttp2_session_reprioritize_stream(session, stream,
&frame->priority.pri_spec); &frame->priority.pri_spec);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return rv; return rv;
} }
}
break; break;
} }
case NGHTTP2_RST_STREAM: case NGHTTP2_RST_STREAM:
@ -3202,26 +3269,32 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
return session_handle_invalid_connection return session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream_id == 0"); (session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream_id == 0");
} }
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if(!stream) { if(!session->server) {
if(session_detect_idle_stream(session, frame->hd.stream_id)) { /* Re-prioritization works only in server */
return session_handle_invalid_connection return session_call_on_frame_received(session, frame);
(session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream in idle");
} }
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if(!stream) {
/* PRIORITY against idle stream can create anchor node in
dependency tree. */
if(!session_detect_idle_stream(session, frame->hd.stream_id) ||
session_closed_stream_full(session)) {
return 0; return 0;
} }
if(state_reserved_remote(session, stream)) {
return session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream in reserved");
}
/* Only update priority of the stream, only if it is not pushed
stream and is initiated by remote peer, or it is pushed stream
and is initiated by local peer */
if(((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) == 0 &&
!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) ||
((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) &&
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id))) {
stream = nghttp2_session_open_stream(session, frame->hd.stream_id,
NGHTTP2_STREAM_FLAG_NONE,
&frame->priority.pri_spec,
NGHTTP2_STREAM_IDLE,
NULL);
if(stream == NULL) {
return NGHTTP2_ERR_NOMEM;
}
} else {
rv = nghttp2_session_reprioritize_stream(session, stream, rv = nghttp2_session_reprioritize_stream(session, stream,
&frame->priority.pri_spec); &frame->priority.pri_spec);
@ -3229,6 +3302,7 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
return rv; return rv;
} }
} }
return session_call_on_frame_received(session, frame); return session_call_on_frame_received(session, frame);
} }

View File

@ -425,6 +425,12 @@ void nghttp2_session_destroy_stream(nghttp2_session *session,
void nghttp2_session_keep_closed_stream(nghttp2_session *session, void nghttp2_session_keep_closed_stream(nghttp2_session *session,
nghttp2_stream *stream); nghttp2_stream *stream);
/*
* Detaches |stream| from closed streams linked list.
*/
void nghttp2_session_detach_closed_stream(nghttp2_session *session,
nghttp2_stream *stream);
/* /*
* Deletes closed stream to ensure that number of incoming streams * Deletes closed stream to ensure that number of incoming streams
* including active and closed is in the maximum number of allowed * including active and closed is in the maximum number of allowed
@ -644,7 +650,8 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session,
/* /*
* This function behaves like nghttp2_session_get_stream(), but it * This function behaves like nghttp2_session_get_stream(), but it
* returns stream object even if it is marked as closed. * returns stream object even if it is marked as closed or in
* NGHTTP2_STREAM_IDLE state.
*/ */
nghttp2_stream* nghttp2_session_get_stream_raw(nghttp2_session *session, nghttp2_stream* nghttp2_session_get_stream_raw(nghttp2_session *session,
int32_t stream_id); int32_t stream_id);

View File

@ -57,6 +57,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
stream->sib_prev = NULL; stream->sib_prev = NULL;
stream->sib_next = NULL; stream->sib_next = NULL;
stream->closed_prev = NULL;
stream->closed_next = NULL; stream->closed_next = NULL;
stream->dpri = NGHTTP2_STREAM_DPRI_NO_DATA; stream->dpri = NGHTTP2_STREAM_DPRI_NO_DATA;

View File

@ -65,7 +65,10 @@ typedef enum {
memory. */ memory. */
NGHTTP2_STREAM_CLOSING, NGHTTP2_STREAM_CLOSING,
/* PUSH_PROMISE is received or sent */ /* PUSH_PROMISE is received or sent */
NGHTTP2_STREAM_RESERVED NGHTTP2_STREAM_RESERVED,
/* Stream is created in this state if it is used as anchor in
dependency tree. */
NGHTTP2_STREAM_IDLE
} nghttp2_stream_state; } nghttp2_stream_state;
typedef enum { typedef enum {
@ -126,11 +129,11 @@ struct nghttp2_stream {
doubly-linked list and first element is pointed by doubly-linked list and first element is pointed by
roots->head. */ roots->head. */
nghttp2_stream *root_prev, *root_next; nghttp2_stream *root_prev, *root_next;
/* When stream is kept after closure, it may be kept in single /* When stream is kept after closure, it may be kept in doubly
linked list pointed by nghttp2_session closed_stream_head. linked list pointed by nghttp2_session closed_stream_head.
closed_next points to the next stream object if it is the element closed_next points to the next stream object if it is the element
of the list. */ of the list. */
nghttp2_stream *closed_next; nghttp2_stream *closed_prev, *closed_next;
/* pointer to roots, which tracks dependency tree roots */ /* pointer to roots, which tracks dependency tree roots */
nghttp2_stream_roots *roots; nghttp2_stream_roots *roots;
/* The arbitrary data provided by user for this stream. */ /* The arbitrary data provided by user for this stream. */

View File

@ -243,6 +243,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_stream_attach_data_subtree) || test_nghttp2_session_stream_attach_data_subtree) ||
!CU_add_test(pSuite, "session_stream_keep_closed_stream", !CU_add_test(pSuite, "session_stream_keep_closed_stream",
test_nghttp2_session_keep_closed_stream) || test_nghttp2_session_keep_closed_stream) ||
!CU_add_test(pSuite, "session_detach_closed_stream",
test_nghttp2_session_detach_closed_stream) ||
!CU_add_test(pSuite, "session_large_dep_tree", !CU_add_test(pSuite, "session_large_dep_tree",
test_nghttp2_session_large_dep_tree) || test_nghttp2_session_large_dep_tree) ||
!CU_add_test(pSuite, "session_graceful_shutdown", !CU_add_test(pSuite, "session_graceful_shutdown",
@ -253,6 +255,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_recv_client_preface) || test_nghttp2_session_recv_client_preface) ||
!CU_add_test(pSuite, "session_delete_data_item", !CU_add_test(pSuite, "session_delete_data_item",
test_nghttp2_session_delete_data_item) || test_nghttp2_session_delete_data_item) ||
!CU_add_test(pSuite, "session_open_idle_stream",
test_nghttp2_session_open_idle_stream) ||
!CU_add_test(pSuite, "frame_pack_headers", !CU_add_test(pSuite, "frame_pack_headers",
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",

View File

@ -2030,16 +2030,7 @@ void test_nghttp2_session_on_priority_received(void)
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
/* non-push and initiated by remote peer */ /* depend on stream 0 */
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
CU_ASSERT(2 == stream->weight);
/* push and initiated by remote peer: no update */
stream->flags = NGHTTP2_STREAM_FLAG_PUSH;
frame.priority.pri_spec.weight = 3;
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
CU_ASSERT(2 == stream->weight); CU_ASSERT(2 == stream->weight);
@ -2054,35 +2045,23 @@ void test_nghttp2_session_on_priority_received(void)
NGHTTP2_STREAM_OPENING, NULL); NGHTTP2_STREAM_OPENING, NULL);
frame.hd.stream_id = 2; frame.hd.stream_id = 2;
/* non-push and initiated by local peer: no update */
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
/* push and initiated by local peer */
stream->flags = NGHTTP2_STREAM_FLAG_PUSH;
/* using dependency stream */
nghttp2_priority_spec_init(&frame.priority.pri_spec, 3, 1, 0); nghttp2_priority_spec_init(&frame.priority.pri_spec, 3, 1, 0);
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
CU_ASSERT(dep_stream == stream->dep_prev); CU_ASSERT(dep_stream == stream->dep_prev);
nghttp2_frame_priority_free(&frame.priority); /* PRIORITY against idle stream */
nghttp2_session_del(session);
/* Check that receiving PRIORITY in reserved(remote) is error */ frame.hd.stream_id = 100;
nghttp2_session_server_new(&session, &callbacks, &user_data);
nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default,
NGHTTP2_STREAM_RESERVED, NULL);
nghttp2_frame_priority_init(&frame.priority, 3, &pri_spec_default);
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); stream = nghttp2_session_get_stream_raw(session, frame.hd.stream_id);
CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
CU_ASSERT(dep_stream == stream->dep_prev);
nghttp2_frame_priority_free(&frame.priority); nghttp2_frame_priority_free(&frame.priority);
nghttp2_session_del(session); nghttp2_session_del(session);
@ -3743,7 +3722,6 @@ void test_nghttp2_submit_priority(void)
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback; callbacks.send_callback = null_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback; callbacks.on_frame_send_callback = on_frame_send_callback;
callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
nghttp2_session_client_new(&session, &callbacks, &ud); nghttp2_session_client_new(&session, &callbacks, &ud);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
@ -3752,56 +3730,19 @@ void test_nghttp2_submit_priority(void)
nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); nghttp2_priority_spec_init(&pri_spec, 0, 3, 0);
/* non-push stream and initiated by local peer */ /* depends on stream 0 */
CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1,
&pri_spec)); &pri_spec));
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(3 == stream->weight); CU_ASSERT(3 == stream->weight);
/* push stream and initiated by local peer: no update */ /* submit against idle stream */
stream->flags = NGHTTP2_STREAM_FLAG_PUSH; CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 3,
nghttp2_priority_spec_init(&pri_spec, 0, 2, 0);
CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1,
&pri_spec));
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(3 == stream->weight);
/* non-push stream and initiated by remote peer: no update */
stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default,
NGHTTP2_STREAM_OPENING, NULL);
CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2,
&pri_spec));
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
/* push stream and initiated by remote peer */
stream->flags = NGHTTP2_STREAM_FLAG_PUSH;
CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2,
&pri_spec));
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(2 == stream->weight);
nghttp2_session_del(session);
/* Check that transmission of PRIORITY in reserved(local) is
error */
nghttp2_session_server_new(&session, &callbacks, &ud);
stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default,
NGHTTP2_STREAM_RESERVED, NULL);
CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2,
&pri_spec)); &pri_spec));
ud.frame_send_cb_called = 0; ud.frame_send_cb_called = 0;
ud.frame_not_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(0 == ud.frame_send_cb_called); CU_ASSERT(1 == ud.frame_send_cb_called);
CU_ASSERT(1 == ud.frame_not_send_cb_called);
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -6359,7 +6300,6 @@ void test_nghttp2_session_keep_closed_stream(void)
{ {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
/* nghttp2_stream *stream; */
const size_t max_concurrent_streams = 5; const size_t max_concurrent_streams = 5;
nghttp2_settings_entry iv = { nghttp2_settings_entry iv = {
NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
@ -6391,12 +6331,20 @@ void test_nghttp2_session_keep_closed_stream(void)
CU_ASSERT(2 == session->num_closed_streams); CU_ASSERT(2 == session->num_closed_streams);
CU_ASSERT(5 == session->closed_stream_tail->stream_id); CU_ASSERT(5 == session->closed_stream_tail->stream_id);
CU_ASSERT(1 == session->closed_stream_head->stream_id); CU_ASSERT(1 == session->closed_stream_head->stream_id);
CU_ASSERT(session->closed_stream_head ==
session->closed_stream_tail->closed_prev);
CU_ASSERT(NULL == session->closed_stream_tail->closed_next);
CU_ASSERT(session->closed_stream_tail ==
session->closed_stream_head->closed_next);
CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
open_stream(session, 11); open_stream(session, 11);
CU_ASSERT(1 == session->num_closed_streams); CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(5 == session->closed_stream_tail->stream_id); CU_ASSERT(5 == session->closed_stream_tail->stream_id);
CU_ASSERT(session->closed_stream_tail == session->closed_stream_head); CU_ASSERT(session->closed_stream_tail == session->closed_stream_head);
CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
CU_ASSERT(NULL == session->closed_stream_head->closed_next);
open_stream(session, 13); open_stream(session, 13);
@ -6407,6 +6355,88 @@ void test_nghttp2_session_keep_closed_stream(void)
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_session_detach_closed_stream(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
int i;
nghttp2_stream *stream;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_server_new(&session, &callbacks, NULL);
for(i = 0; i < 3; ++i) {
open_stream(session, i);
nghttp2_session_close_stream(session, i, NGHTTP2_NO_ERROR);
}
CU_ASSERT(3 == session->num_closed_streams);
stream = nghttp2_session_get_stream_raw(session, 1);
CU_ASSERT(session->closed_stream_head == stream->closed_prev);
CU_ASSERT(session->closed_stream_tail == stream->closed_next);
CU_ASSERT(stream == session->closed_stream_head->closed_next);
CU_ASSERT(stream == session->closed_stream_tail->closed_prev);
nghttp2_session_detach_closed_stream(session, stream);
CU_ASSERT(2 == session->num_closed_streams);
CU_ASSERT(NULL == stream->closed_prev);
CU_ASSERT(NULL == stream->closed_next);
CU_ASSERT(session->closed_stream_head ==
session->closed_stream_tail->closed_prev);
CU_ASSERT(session->closed_stream_tail ==
session->closed_stream_head->closed_next);
/* Close head stream */
stream = session->closed_stream_head;
nghttp2_session_detach_closed_stream(session, stream);
CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(session->closed_stream_head == session->closed_stream_tail);
CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
CU_ASSERT(NULL == session->closed_stream_head->closed_next);
/* Close last stream */
stream = session->closed_stream_head;
nghttp2_session_detach_closed_stream(session, stream);
CU_ASSERT(0 == session->num_closed_streams);
CU_ASSERT(NULL == session->closed_stream_head);
CU_ASSERT(NULL == session->closed_stream_tail);
for(i = 3; i < 5; ++i) {
open_stream(session, i);
nghttp2_session_close_stream(session, i, NGHTTP2_NO_ERROR);
}
CU_ASSERT(2 == session->num_closed_streams);
/* Close tail stream */
stream = session->closed_stream_tail;
nghttp2_session_detach_closed_stream(session, stream);
CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(session->closed_stream_head == session->closed_stream_tail);
CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
CU_ASSERT(NULL == session->closed_stream_head->closed_next);
nghttp2_session_del(session);
}
void test_nghttp2_session_large_dep_tree(void) void test_nghttp2_session_large_dep_tree(void)
{ {
nghttp2_session *session; nghttp2_session *session;
@ -6635,3 +6665,47 @@ void test_nghttp2_session_delete_data_item(void)
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_session_open_idle_stream(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_stream *stream;
nghttp2_stream *opened_stream;
nghttp2_priority_spec pri_spec;
nghttp2_frame frame;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
nghttp2_priority_spec_init(&pri_spec, 0, 3, 0);
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
stream = nghttp2_session_get_stream_raw(session, 1);
CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
CU_ASSERT(NULL == stream->closed_prev);
CU_ASSERT(NULL == stream->closed_next);
CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(session->closed_stream_head == stream);
CU_ASSERT(session->closed_stream_tail == stream);
opened_stream = nghttp2_session_open_stream(session, 1,
NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default,
NGHTTP2_STREAM_OPENING, NULL);
CU_ASSERT(stream == opened_stream);
CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
CU_ASSERT(0 == session->num_closed_streams);
CU_ASSERT(NULL == session->closed_stream_head);
CU_ASSERT(NULL == session->closed_stream_tail);
nghttp2_frame_priority_free(&frame.priority);
nghttp2_session_del(session);
}

View File

@ -111,10 +111,12 @@ void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void);
void test_nghttp2_session_stream_attach_data(void); void test_nghttp2_session_stream_attach_data(void);
void test_nghttp2_session_stream_attach_data_subtree(void); void test_nghttp2_session_stream_attach_data_subtree(void);
void test_nghttp2_session_keep_closed_stream(void); void test_nghttp2_session_keep_closed_stream(void);
void test_nghttp2_session_detach_closed_stream(void);
void test_nghttp2_session_large_dep_tree(void); void test_nghttp2_session_large_dep_tree(void);
void test_nghttp2_session_graceful_shutdown(void); void test_nghttp2_session_graceful_shutdown(void);
void test_nghttp2_session_on_header_temporal_failure(void); void test_nghttp2_session_on_header_temporal_failure(void);
void test_nghttp2_session_recv_client_preface(void); void test_nghttp2_session_recv_client_preface(void);
void test_nghttp2_session_delete_data_item(void); void test_nghttp2_session_delete_data_item(void);
void test_nghttp2_session_open_idle_stream(void);
#endif /* NGHTTP2_SESSION_TEST_H */ #endif /* NGHTTP2_SESSION_TEST_H */