1266 lines
33 KiB
C
1266 lines
33 KiB
C
/*
|
|
* nghttp2 - HTTP/2 C Library
|
|
*
|
|
* Copyright (c) 2012 Tatsuhiro Tsujikawa
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include "nghttp2_stream.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
#include "nghttp2_session.h"
|
|
#include "nghttp2_helper.h"
|
|
|
|
void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
|
uint8_t flags, nghttp2_stream_state initial_state,
|
|
int32_t weight, nghttp2_stream_roots *roots,
|
|
int32_t remote_initial_window_size,
|
|
int32_t local_initial_window_size,
|
|
void *stream_user_data) {
|
|
nghttp2_map_entry_init(&stream->map_entry, stream_id);
|
|
stream->stream_id = stream_id;
|
|
stream->flags = flags;
|
|
stream->state = initial_state;
|
|
stream->shut_flags = NGHTTP2_SHUT_NONE;
|
|
stream->stream_user_data = stream_user_data;
|
|
stream->item = NULL;
|
|
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->dep_prev = NULL;
|
|
stream->dep_next = NULL;
|
|
stream->sib_prev = NULL;
|
|
stream->sib_next = NULL;
|
|
|
|
stream->closed_prev = NULL;
|
|
stream->closed_next = NULL;
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_NO_ITEM;
|
|
stream->num_substreams = 1;
|
|
stream->weight = weight;
|
|
stream->sum_dep_weight = 0;
|
|
stream->sum_norest_weight = 0;
|
|
|
|
stream->roots = roots;
|
|
stream->root_prev = NULL;
|
|
stream->root_next = NULL;
|
|
|
|
stream->http_flags = NGHTTP2_HTTP_FLAG_NONE;
|
|
stream->content_length = -1;
|
|
stream->recv_content_length = 0;
|
|
stream->status_code = -1;
|
|
}
|
|
|
|
void nghttp2_stream_free(nghttp2_stream *stream _U_) {
|
|
/* We don't free stream->item. If it is assigned to aob, then
|
|
active_outbound_item_reset() will delete it. If it is queued,
|
|
then it is deleted when pq is deleted in nghttp2_session_del().
|
|
Otherwise, nghttp2_session_del() will delete it. */
|
|
}
|
|
|
|
void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) {
|
|
stream->shut_flags |= flag;
|
|
}
|
|
|
|
static int stream_push_item(nghttp2_stream *stream, nghttp2_session *session) {
|
|
/* This is required for Android NDK r10d */
|
|
int rv = 0;
|
|
nghttp2_outbound_item *item;
|
|
|
|
assert(stream->item);
|
|
assert(stream->item->queued == 0);
|
|
|
|
item = stream->item;
|
|
|
|
/* If item is now sent, don't push it to the queue. Otherwise, we
|
|
may push same item twice. */
|
|
if (session->aob.item == item) {
|
|
return 0;
|
|
}
|
|
|
|
switch (item->frame.hd.type) {
|
|
case NGHTTP2_DATA:
|
|
/* Penalize item by delaying scheduling according to effective
|
|
weight. This will delay low priority stream, which is good.
|
|
OTOH, this may incur delay for high priority item. Will
|
|
see. */
|
|
item->cycle = session->last_cycle +
|
|
NGHTTP2_DATA_PAYLOADLEN * NGHTTP2_MAX_WEIGHT /
|
|
nghttp2_stream_compute_effective_weight(stream);
|
|
|
|
rv = nghttp2_pq_push(&session->ob_da_pq, item);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
break;
|
|
case NGHTTP2_HEADERS:
|
|
if (stream->state == NGHTTP2_STREAM_RESERVED) {
|
|
nghttp2_outbound_queue_push(&session->ob_syn, item);
|
|
} else {
|
|
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
|
}
|
|
break;
|
|
default:
|
|
/* should not reach here */
|
|
assert(0);
|
|
}
|
|
|
|
item->queued = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
|
|
for (; stream->sib_next; stream = stream->sib_next)
|
|
;
|
|
|
|
return stream;
|
|
}
|
|
|
|
static void stream_update_dep_length(nghttp2_stream *stream, ssize_t delta) {
|
|
stream->num_substreams += delta;
|
|
|
|
if (stream->dep_prev) {
|
|
stream_update_dep_length(stream->dep_prev, delta);
|
|
}
|
|
}
|
|
|
|
int32_t nghttp2_stream_dep_distributed_weight(nghttp2_stream *stream,
|
|
int32_t weight) {
|
|
weight = stream->weight * weight / stream->sum_dep_weight;
|
|
|
|
return nghttp2_max(1, weight);
|
|
}
|
|
|
|
static void stream_update_dep_set_rest(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
|
|
if (stream == NULL) {
|
|
return;
|
|
}
|
|
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d is rest\n", stream->stream_id));
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
|
|
return;
|
|
}
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_REST;
|
|
return;
|
|
}
|
|
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
stream_update_dep_set_rest(si);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Performs dfs starting |stream|, search stream which can become
|
|
* NGHTTP2_STREAM_DPRI_TOP and set its dpri. This function also
|
|
* updates sum_norest_weight if stream->dpri ==
|
|
* NGHTTP2_STREAM_DPRI_NO_ITEM. This function returns nonzero if
|
|
* stream's subtree contains at least one NGHTTP2_STRAEM_DPRI_TOP
|
|
* stream.
|
|
*/
|
|
static int stream_update_dep_set_top(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
|
|
if (!stream) {
|
|
return 0;
|
|
}
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
return 1;
|
|
}
|
|
|
|
stream->sum_norest_weight = 0;
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
|
|
DEBUGF(
|
|
fprintf(stderr, "stream: stream=%d item is top\n", stream->stream_id));
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_TOP;
|
|
|
|
return 1;
|
|
}
|
|
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
if (stream_update_dep_set_top(si)) {
|
|
stream->sum_norest_weight += si->weight;
|
|
}
|
|
}
|
|
|
|
return stream->sum_norest_weight > 0;
|
|
}
|
|
|
|
/*
|
|
* Performs dfs starting |stream|, and dueue stream whose dpri is
|
|
* NGHTTP2_STREAM_DPRI_TOP and has not been queued yet.
|
|
*
|
|
* This function returns 0 if it succeeds, or one of the following
|
|
* negative error codes:
|
|
*
|
|
* NGHTTP2_ERR_NOMEM
|
|
* Out of memory.
|
|
*/
|
|
static int stream_update_dep_queue_top(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
int rv;
|
|
nghttp2_stream *si;
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
|
|
return 0;
|
|
}
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
if (!stream->item->queued) {
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d enqueue\n", stream->stream_id));
|
|
rv = stream_push_item(stream, session);
|
|
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (stream->sum_norest_weight == 0) {
|
|
return 0;
|
|
}
|
|
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
rv = stream_update_dep_queue_top(si, session);
|
|
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Updates stream->sum_norest_weight recursively towards root.
|
|
* |delta| must not be 0. We have to gather effective sum of weight
|
|
* of descendants. |delta| is added to stream->sum_norest_weight. If
|
|
* stream->sum_norest_weight becomes 0, we have to update parent
|
|
* stream, decreasing its sum_norest_weight by stream->weight. If
|
|
* stream->sum_norest_weight becomes from 0 to positive, then we have
|
|
* to update parent stream, increasing its sum_norest_weight by
|
|
* stream->weight. Otherwise, we stop recursive call.
|
|
*/
|
|
static void stream_update_dep_sum_norest_weight(nghttp2_stream *stream,
|
|
int32_t delta) {
|
|
int32_t old;
|
|
|
|
if (!stream) {
|
|
return;
|
|
}
|
|
|
|
assert(delta != 0);
|
|
assert(stream->sum_norest_weight + delta >= 0);
|
|
|
|
old = stream->sum_norest_weight;
|
|
stream->sum_norest_weight += delta;
|
|
|
|
if (old == 0) {
|
|
assert(delta > 0);
|
|
stream_update_dep_sum_norest_weight(stream->dep_prev, stream->weight);
|
|
return;
|
|
}
|
|
|
|
assert(old > 0);
|
|
|
|
if (stream->sum_norest_weight == 0) {
|
|
stream_update_dep_sum_norest_weight(stream->dep_prev, -stream->weight);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns stream whose dpri is NGHTTP2_STREAM_DPRI_NO_ITEM along the
|
|
* path following stream->dep_prev (stream's ancestors, including
|
|
* itself). In other words, find stream which blocks the descendant
|
|
* streams. If there is no such stream, returns NULL.
|
|
*/
|
|
static nghttp2_stream *stream_get_dep_blocking(nghttp2_stream *stream) {
|
|
for (; stream; stream = stream->dep_prev) {
|
|
if (stream->dpri != NGHTTP2_STREAM_DPRI_NO_ITEM) {
|
|
return stream;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef STREAM_DEP_DEBUG
|
|
|
|
static size_t check_stream_num(nghttp2_stream *stream) {
|
|
size_t n = 1;
|
|
nghttp2_stream *si;
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
n += check_stream_num(si);
|
|
}
|
|
if (n != stream->num_substreams) {
|
|
fprintf(stderr, "num_substreams = %zu; want %zu\n", n,
|
|
stream->num_substreams);
|
|
|
|
assert(0);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static void ensure_rest_or_no_item(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
switch (stream->dpri) {
|
|
case NGHTTP2_STREAM_DPRI_TOP:
|
|
fprintf(stderr, "NGHTTP2_STREAM_DPRI_TOP; want REST or NO_ITEM\n");
|
|
assert(0);
|
|
break;
|
|
case NGHTTP2_STREAM_DPRI_REST:
|
|
case NGHTTP2_STREAM_DPRI_NO_ITEM:
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
ensure_rest_or_no_item(si);
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "invalid dpri %d\n", stream->dpri);
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
static void check_dpri(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
switch (stream->dpri) {
|
|
case NGHTTP2_STREAM_DPRI_TOP:
|
|
if (!stream->item->queued) {
|
|
fprintf(stderr, "stream->item->queued is not nonzero while it is in "
|
|
"NGHTTP2_STREAM_DPRI_TOP\n");
|
|
assert(0);
|
|
}
|
|
/* fall through */
|
|
case NGHTTP2_STREAM_DPRI_REST:
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
ensure_rest_or_no_item(si);
|
|
}
|
|
break;
|
|
case NGHTTP2_STREAM_DPRI_NO_ITEM:
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
check_dpri(si);
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "invalid dpri %d\n", stream->dpri);
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
static void check_sum_dep(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
int32_t n = 0;
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
n += si->weight;
|
|
}
|
|
if (n != stream->sum_dep_weight) {
|
|
fprintf(stderr, "sum_dep_weight = %d; want %d\n", n,
|
|
stream->sum_dep_weight);
|
|
assert(0);
|
|
}
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
check_sum_dep(si);
|
|
}
|
|
}
|
|
|
|
static int check_sum_norest(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
int32_t n = 0;
|
|
switch (stream->dpri) {
|
|
case NGHTTP2_STREAM_DPRI_TOP:
|
|
return 1;
|
|
case NGHTTP2_STREAM_DPRI_REST:
|
|
return 0;
|
|
case NGHTTP2_STREAM_DPRI_NO_ITEM:
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
if (check_sum_norest(si)) {
|
|
n += si->weight;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "invalid dpri %d\n", stream->dpri);
|
|
assert(0);
|
|
}
|
|
if (n != stream->sum_norest_weight) {
|
|
fprintf(stderr, "sum_norest_weight = %d; want %d\n", n,
|
|
stream->sum_norest_weight);
|
|
assert(0);
|
|
}
|
|
return n > 0;
|
|
}
|
|
|
|
static void check_dep_prev(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
if (si->dep_prev != stream) {
|
|
fprintf(stderr, "si->dep_prev = %p; want %p\n", si->dep_prev, stream);
|
|
assert(0);
|
|
}
|
|
check_dep_prev(si);
|
|
}
|
|
}
|
|
|
|
#endif /* STREAM_DEP_DEBUG */
|
|
|
|
static void validate_tree(nghttp2_stream *stream) {
|
|
#ifdef STREAM_DEP_DEBUG
|
|
if (!stream) {
|
|
return;
|
|
}
|
|
|
|
for (; stream->dep_prev; stream = stream->dep_prev)
|
|
;
|
|
|
|
check_stream_num(stream);
|
|
check_dpri(stream);
|
|
check_sum_dep(stream);
|
|
check_sum_norest(stream);
|
|
check_dep_prev(stream);
|
|
#endif /* STREAM_DEP_DEBUG*/
|
|
}
|
|
|
|
static int stream_update_dep_on_attach_item(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *blocking_stream, *si;
|
|
int rv;
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_REST;
|
|
|
|
blocking_stream = stream_get_dep_blocking(stream->dep_prev);
|
|
|
|
/* If we found REST or TOP in ascendants, we don't have to update
|
|
any metadata. */
|
|
if (blocking_stream) {
|
|
validate_tree(stream);
|
|
return 0;
|
|
}
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_TOP;
|
|
if (stream->sum_norest_weight == 0) {
|
|
stream_update_dep_sum_norest_weight(stream->dep_prev, stream->weight);
|
|
} else {
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
stream_update_dep_set_rest(si);
|
|
}
|
|
}
|
|
|
|
if (!stream->item->queued) {
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d enqueue\n", stream->stream_id));
|
|
rv = stream_push_item(stream, session);
|
|
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
validate_tree(stream);
|
|
return 0;
|
|
}
|
|
|
|
static int stream_update_dep_on_detach_item(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
int rv;
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_NO_ITEM;
|
|
validate_tree(stream);
|
|
return 0;
|
|
}
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM) {
|
|
/* nghttp2_stream_defer_item() does not clear stream->item, but
|
|
set dpri = NGHTTP2_STREAM_DPRI_NO_ITEM. Catch this case
|
|
here. */
|
|
validate_tree(stream);
|
|
return 0;
|
|
}
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_NO_ITEM;
|
|
|
|
if (stream_update_dep_set_top(stream) == 0) {
|
|
stream_update_dep_sum_norest_weight(stream->dep_prev, -stream->weight);
|
|
validate_tree(stream);
|
|
return 0;
|
|
}
|
|
|
|
rv = stream_update_dep_queue_top(stream, session);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
validate_tree(stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nghttp2_stream_attach_item(nghttp2_stream *stream,
|
|
nghttp2_outbound_item *item,
|
|
nghttp2_session *session) {
|
|
assert((stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) == 0);
|
|
assert(stream->item == NULL);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d attach item=%p\n",
|
|
stream->stream_id, item));
|
|
|
|
stream->item = item;
|
|
|
|
return stream_update_dep_on_attach_item(stream, session);
|
|
}
|
|
|
|
int nghttp2_stream_detach_item(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d detach item=%p\n",
|
|
stream->stream_id, stream->item));
|
|
|
|
stream->item = NULL;
|
|
stream->flags &= ~NGHTTP2_STREAM_FLAG_DEFERRED_ALL;
|
|
|
|
return stream_update_dep_on_detach_item(stream, session);
|
|
}
|
|
|
|
int nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags,
|
|
nghttp2_session *session) {
|
|
assert(stream->item);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d defer item=%p cause=%02x\n",
|
|
stream->stream_id, stream->item, flags));
|
|
|
|
stream->flags |= flags;
|
|
|
|
return stream_update_dep_on_detach_item(stream, session);
|
|
}
|
|
|
|
int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags,
|
|
nghttp2_session *session) {
|
|
assert(stream->item);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d resume item=%p flags=%02x\n",
|
|
stream->stream_id, stream->item, flags));
|
|
|
|
stream->flags &= ~flags;
|
|
|
|
if (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) {
|
|
return 0;
|
|
}
|
|
|
|
return stream_update_dep_on_attach_item(stream, session);
|
|
}
|
|
|
|
int nghttp2_stream_check_deferred_item(nghttp2_stream *stream) {
|
|
return stream->item && (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL);
|
|
}
|
|
|
|
int nghttp2_stream_check_deferred_by_flow_control(nghttp2_stream *stream) {
|
|
return stream->item &&
|
|
(stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
|
|
}
|
|
|
|
static int update_initial_window_size(int32_t *window_size_ptr,
|
|
int32_t new_initial_window_size,
|
|
int32_t old_initial_window_size) {
|
|
int64_t new_window_size = (int64_t)(*window_size_ptr) +
|
|
new_initial_window_size - old_initial_window_size;
|
|
if (INT32_MIN > new_window_size ||
|
|
new_window_size > NGHTTP2_MAX_WINDOW_SIZE) {
|
|
return -1;
|
|
}
|
|
*window_size_ptr = (int32_t)new_window_size;
|
|
return 0;
|
|
}
|
|
|
|
int nghttp2_stream_update_remote_initial_window_size(
|
|
nghttp2_stream *stream, int32_t new_initial_window_size,
|
|
int32_t old_initial_window_size) {
|
|
return update_initial_window_size(&stream->remote_window_size,
|
|
new_initial_window_size,
|
|
old_initial_window_size);
|
|
}
|
|
|
|
int nghttp2_stream_update_local_initial_window_size(
|
|
nghttp2_stream *stream, int32_t new_initial_window_size,
|
|
int32_t old_initial_window_size) {
|
|
return update_initial_window_size(&stream->local_window_size,
|
|
new_initial_window_size,
|
|
old_initial_window_size);
|
|
}
|
|
|
|
void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) {
|
|
stream->state = NGHTTP2_STREAM_OPENED;
|
|
stream->flags &= ~NGHTTP2_STREAM_FLAG_PUSH;
|
|
}
|
|
|
|
nghttp2_stream *nghttp2_stream_get_dep_root(nghttp2_stream *stream) {
|
|
for (; stream->dep_prev; stream = stream->dep_prev)
|
|
;
|
|
return stream;
|
|
}
|
|
|
|
int nghttp2_stream_dep_subtree_find(nghttp2_stream *stream,
|
|
nghttp2_stream *target) {
|
|
if (stream == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (stream == target) {
|
|
return 1;
|
|
}
|
|
|
|
if (nghttp2_stream_dep_subtree_find(stream->sib_next, target)) {
|
|
return 1;
|
|
}
|
|
|
|
return nghttp2_stream_dep_subtree_find(stream->dep_next, target);
|
|
}
|
|
|
|
int32_t nghttp2_stream_compute_effective_weight(nghttp2_stream *stream) {
|
|
int32_t weight;
|
|
|
|
if (!stream->dep_prev) {
|
|
return stream->weight;
|
|
}
|
|
|
|
weight = nghttp2_stream_compute_effective_weight(stream->dep_prev) *
|
|
stream->weight / stream->dep_prev->sum_norest_weight;
|
|
|
|
return nghttp2_max(1, weight);
|
|
}
|
|
|
|
void nghttp2_stream_dep_insert(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
nghttp2_stream *blocking_stream;
|
|
|
|
assert(stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM);
|
|
|
|
DEBUGF(fprintf(stderr,
|
|
"stream: dep_insert dep_stream(%p)=%d, stream(%p)=%d\n",
|
|
dep_stream, dep_stream->stream_id, stream, stream->stream_id));
|
|
|
|
stream->sum_dep_weight = dep_stream->sum_dep_weight;
|
|
dep_stream->sum_dep_weight = stream->weight;
|
|
|
|
blocking_stream = stream_get_dep_blocking(dep_stream);
|
|
|
|
stream->sum_norest_weight = 0;
|
|
|
|
if (dep_stream->dep_next) {
|
|
assert(dep_stream->num_substreams >= 1);
|
|
/* num_substreams includes node itself */
|
|
stream->num_substreams = dep_stream->num_substreams;
|
|
|
|
for (si = dep_stream->dep_next; si; si = si->sib_next) {
|
|
si->dep_prev = stream;
|
|
if (!blocking_stream && (si->dpri == NGHTTP2_STREAM_DPRI_TOP ||
|
|
(si->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM &&
|
|
si->sum_norest_weight))) {
|
|
stream->sum_norest_weight += si->weight;
|
|
}
|
|
}
|
|
|
|
stream->dep_next = dep_stream->dep_next;
|
|
}
|
|
|
|
dep_stream->dep_next = stream;
|
|
stream->dep_prev = dep_stream;
|
|
|
|
if (stream->sum_norest_weight) {
|
|
dep_stream->sum_norest_weight = stream->weight;
|
|
}
|
|
|
|
stream_update_dep_length(dep_stream, 1);
|
|
|
|
++stream->roots->num_streams;
|
|
|
|
validate_tree(stream);
|
|
}
|
|
|
|
static void set_dep_prev(nghttp2_stream *stream, nghttp2_stream *dep) {
|
|
for (; stream; stream = stream->sib_next) {
|
|
stream->dep_prev = dep;
|
|
}
|
|
}
|
|
|
|
static void link_dep(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
|
dep_stream->dep_next = stream;
|
|
if (stream) {
|
|
stream->dep_prev = dep_stream;
|
|
}
|
|
}
|
|
|
|
static void link_sib(nghttp2_stream *a, nghttp2_stream *b) {
|
|
a->sib_next = b;
|
|
if (b) {
|
|
b->sib_prev = a;
|
|
}
|
|
}
|
|
|
|
static void insert_link_dep(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream) {
|
|
nghttp2_stream *sib_next;
|
|
|
|
assert(stream->sib_prev == NULL);
|
|
|
|
sib_next = dep_stream->dep_next;
|
|
|
|
link_sib(stream, sib_next);
|
|
|
|
link_dep(dep_stream, stream);
|
|
}
|
|
|
|
static void unlink_sib(nghttp2_stream *stream) {
|
|
nghttp2_stream *prev, *next, *dep_next;
|
|
|
|
prev = stream->sib_prev;
|
|
dep_next = stream->dep_next;
|
|
|
|
assert(prev);
|
|
|
|
if (dep_next) {
|
|
/*
|
|
* prev--stream(--sib_next--...)
|
|
* |
|
|
* dep_next
|
|
*/
|
|
|
|
link_sib(prev, dep_next);
|
|
|
|
set_dep_prev(dep_next, stream->dep_prev);
|
|
|
|
if (stream->sib_next) {
|
|
link_sib(stream_last_sib(dep_next), stream->sib_next);
|
|
}
|
|
} else {
|
|
/*
|
|
* prev--stream(--sib_next--...)
|
|
*/
|
|
next = stream->sib_next;
|
|
|
|
prev->sib_next = next;
|
|
|
|
if (next) {
|
|
next->sib_prev = prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void unlink_dep(nghttp2_stream *stream) {
|
|
nghttp2_stream *prev, *next, *dep_next;
|
|
|
|
prev = stream->dep_prev;
|
|
dep_next = stream->dep_next;
|
|
|
|
assert(prev);
|
|
|
|
if (dep_next) {
|
|
/*
|
|
* prev
|
|
* |
|
|
* stream(--sib_next--...)
|
|
* |
|
|
* dep_next
|
|
*/
|
|
link_dep(prev, dep_next);
|
|
|
|
set_dep_prev(dep_next, stream->dep_prev);
|
|
|
|
if (stream->sib_next) {
|
|
link_sib(stream_last_sib(dep_next), stream->sib_next);
|
|
}
|
|
|
|
} else if (stream->sib_next) {
|
|
/*
|
|
* prev
|
|
* |
|
|
* stream--sib_next
|
|
*/
|
|
next = stream->sib_next;
|
|
|
|
next->sib_prev = NULL;
|
|
|
|
link_dep(prev, next);
|
|
} else {
|
|
prev->dep_next = NULL;
|
|
}
|
|
}
|
|
|
|
void nghttp2_stream_dep_add(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream) {
|
|
assert(stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_add dep_stream(%p)=%d, stream(%p)=%d\n",
|
|
dep_stream, dep_stream->stream_id, stream, stream->stream_id));
|
|
|
|
stream_update_dep_length(dep_stream, 1);
|
|
|
|
dep_stream->sum_dep_weight += stream->weight;
|
|
|
|
if (dep_stream->dep_next == NULL) {
|
|
link_dep(dep_stream, stream);
|
|
} else {
|
|
insert_link_dep(dep_stream, stream);
|
|
}
|
|
|
|
++stream->roots->num_streams;
|
|
|
|
validate_tree(stream);
|
|
}
|
|
|
|
void nghttp2_stream_dep_remove(nghttp2_stream *stream) {
|
|
nghttp2_stream *next, *dep_prev, *si, *blocking_stream;
|
|
int32_t sum_dep_weight_delta, sum_norest_weight_delta;
|
|
|
|
assert(stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_remove stream(%p)=%d\n", stream,
|
|
stream->stream_id));
|
|
|
|
blocking_stream = stream_get_dep_blocking(stream->dep_prev);
|
|
|
|
/* Distribute weight of |stream| to direct descendants */
|
|
sum_dep_weight_delta = -stream->weight;
|
|
|
|
sum_norest_weight_delta = 0;
|
|
|
|
/* blocking_stream == NULL means that ascendants are all
|
|
NGHTTP2_STREAM_DPRI_NO_ITEM */
|
|
if (!blocking_stream && stream->sum_norest_weight) {
|
|
sum_norest_weight_delta -= stream->weight;
|
|
}
|
|
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
si->weight = nghttp2_stream_dep_distributed_weight(stream, si->weight);
|
|
|
|
sum_dep_weight_delta += si->weight;
|
|
|
|
if (!blocking_stream &&
|
|
(si->dpri == NGHTTP2_STREAM_DPRI_TOP ||
|
|
(si->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM && si->sum_norest_weight))) {
|
|
sum_norest_weight_delta += si->weight;
|
|
}
|
|
}
|
|
|
|
dep_prev = stream->dep_prev;
|
|
|
|
if (dep_prev) {
|
|
stream_update_dep_length(dep_prev, -1);
|
|
|
|
dep_prev->sum_dep_weight += sum_dep_weight_delta;
|
|
dep_prev->sum_norest_weight += sum_norest_weight_delta;
|
|
}
|
|
|
|
if (stream->sib_prev) {
|
|
unlink_sib(stream);
|
|
validate_tree(stream->sib_prev->dep_prev);
|
|
} else if (stream->dep_prev) {
|
|
unlink_dep(stream);
|
|
validate_tree(stream->dep_prev);
|
|
} else {
|
|
nghttp2_stream_roots_remove(stream->roots, stream);
|
|
|
|
/* stream is a root of tree. Removing stream makes its
|
|
descendants a root of its own subtree. */
|
|
|
|
for (si = stream->dep_next; si;) {
|
|
next = si->sib_next;
|
|
|
|
si->dep_prev = NULL;
|
|
si->sib_prev = NULL;
|
|
si->sib_next = NULL;
|
|
|
|
nghttp2_stream_roots_add(si->roots, si);
|
|
|
|
validate_tree(si);
|
|
|
|
si = next;
|
|
}
|
|
}
|
|
|
|
stream->num_substreams = 1;
|
|
stream->sum_dep_weight = 0;
|
|
stream->sum_norest_weight = 0;
|
|
|
|
stream->dep_prev = NULL;
|
|
stream->dep_next = NULL;
|
|
stream->sib_prev = NULL;
|
|
stream->sib_next = NULL;
|
|
|
|
--stream->roots->num_streams;
|
|
}
|
|
|
|
int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *last_sib;
|
|
nghttp2_stream *dep_next;
|
|
nghttp2_stream *blocking_stream;
|
|
nghttp2_stream *si;
|
|
size_t delta_substreams;
|
|
int rv;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_insert_subtree dep_stream(%p)=%d "
|
|
"stream(%p)=%d\n",
|
|
dep_stream, dep_stream->stream_id, stream, stream->stream_id));
|
|
|
|
delta_substreams = stream->num_substreams;
|
|
|
|
blocking_stream = stream_get_dep_blocking(dep_stream);
|
|
|
|
if (blocking_stream) {
|
|
stream_update_dep_set_rest(stream);
|
|
}
|
|
|
|
if (dep_stream->dep_next) {
|
|
/* dep_stream->num_substreams includes dep_stream itself */
|
|
stream->num_substreams += dep_stream->num_substreams - 1;
|
|
|
|
stream->sum_dep_weight += dep_stream->sum_dep_weight;
|
|
dep_stream->sum_dep_weight = stream->weight;
|
|
|
|
dep_next = dep_stream->dep_next;
|
|
|
|
if (!blocking_stream && dep_stream->sum_norest_weight) {
|
|
for (si = dep_next; si; si = si->sib_next) {
|
|
stream_update_dep_set_rest(si);
|
|
}
|
|
}
|
|
|
|
link_dep(dep_stream, stream);
|
|
|
|
if (stream->dep_next) {
|
|
last_sib = stream_last_sib(stream->dep_next);
|
|
|
|
link_sib(last_sib, dep_next);
|
|
} else {
|
|
link_dep(stream, dep_next);
|
|
}
|
|
|
|
for (si = dep_next; si; si = si->sib_next) {
|
|
si->dep_prev = stream;
|
|
}
|
|
} else {
|
|
link_dep(dep_stream, stream);
|
|
|
|
assert(dep_stream->sum_dep_weight == 0);
|
|
dep_stream->sum_dep_weight = stream->weight;
|
|
}
|
|
|
|
stream_update_dep_length(dep_stream, delta_substreams);
|
|
|
|
if (blocking_stream) {
|
|
validate_tree(dep_stream);
|
|
return 0;
|
|
}
|
|
|
|
if (stream_update_dep_set_top(stream) == 0) {
|
|
validate_tree(dep_stream);
|
|
return 0;
|
|
}
|
|
|
|
dep_stream->sum_norest_weight = stream->weight;
|
|
stream_update_dep_sum_norest_weight(dep_stream->dep_prev, dep_stream->weight);
|
|
|
|
rv = stream_update_dep_queue_top(stream, session);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
validate_tree(dep_stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *blocking_stream;
|
|
int rv;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_add_subtree dep_stream(%p)=%d "
|
|
"stream(%p)=%d\n",
|
|
dep_stream, dep_stream->stream_id, stream, stream->stream_id));
|
|
|
|
if (dep_stream->dep_next) {
|
|
dep_stream->sum_dep_weight += stream->weight;
|
|
|
|
insert_link_dep(dep_stream, stream);
|
|
} else {
|
|
link_dep(dep_stream, stream);
|
|
|
|
assert(dep_stream->sum_dep_weight == 0);
|
|
dep_stream->sum_dep_weight = stream->weight;
|
|
}
|
|
|
|
stream_update_dep_length(dep_stream, stream->num_substreams);
|
|
|
|
blocking_stream = stream_get_dep_blocking(dep_stream);
|
|
|
|
if (blocking_stream) {
|
|
/* We cannot make any assumption for stream if its dpri is not
|
|
NGHTTP2_DPRI_TOP. Just dfs under stream here. */
|
|
stream_update_dep_set_rest(stream);
|
|
|
|
validate_tree(dep_stream);
|
|
return 0;
|
|
}
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
stream_update_dep_sum_norest_weight(dep_stream, stream->weight);
|
|
validate_tree(dep_stream);
|
|
return 0;
|
|
}
|
|
|
|
if (stream_update_dep_set_top(stream) == 0) {
|
|
validate_tree(dep_stream);
|
|
return 0;
|
|
}
|
|
|
|
/* Newly added subtree contributes to dep_stream's
|
|
sum_norest_weight */
|
|
stream_update_dep_sum_norest_weight(dep_stream, stream->weight);
|
|
|
|
rv = stream_update_dep_queue_top(stream, session);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
validate_tree(dep_stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream) {
|
|
nghttp2_stream *next, *dep_prev, *blocking_stream;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_remove_subtree stream(%p)=%d\n", stream,
|
|
stream->stream_id));
|
|
|
|
if (stream->sib_prev) {
|
|
link_sib(stream->sib_prev, stream->sib_next);
|
|
dep_prev = stream->dep_prev;
|
|
} else if (stream->dep_prev) {
|
|
dep_prev = stream->dep_prev;
|
|
next = stream->sib_next;
|
|
|
|
link_dep(dep_prev, next);
|
|
|
|
if (next) {
|
|
next->sib_prev = NULL;
|
|
}
|
|
} else {
|
|
nghttp2_stream_roots_remove(stream->roots, stream);
|
|
|
|
dep_prev = NULL;
|
|
}
|
|
|
|
if (dep_prev) {
|
|
dep_prev->sum_dep_weight -= stream->weight;
|
|
|
|
stream_update_dep_length(dep_prev, -stream->num_substreams);
|
|
|
|
blocking_stream = stream_get_dep_blocking(dep_prev);
|
|
|
|
if (!blocking_stream && (stream->dpri == NGHTTP2_STREAM_DPRI_TOP ||
|
|
(stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM &&
|
|
stream->sum_norest_weight))) {
|
|
stream_update_dep_sum_norest_weight(dep_prev, -stream->weight);
|
|
}
|
|
|
|
validate_tree(dep_prev);
|
|
}
|
|
|
|
stream->sib_prev = NULL;
|
|
stream->sib_next = NULL;
|
|
stream->dep_prev = NULL;
|
|
}
|
|
|
|
int nghttp2_stream_dep_make_root(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
int rv;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_make_root stream(%p)=%d\n", stream,
|
|
stream->stream_id));
|
|
|
|
nghttp2_stream_roots_add(stream->roots, stream);
|
|
|
|
if (stream_update_dep_set_top(stream) == 0) {
|
|
validate_tree(stream);
|
|
return 0;
|
|
}
|
|
|
|
rv = stream_update_dep_queue_top(stream, session);
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
validate_tree(stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
nghttp2_stream_dep_all_your_stream_are_belong_to_us(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *first, *si;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: ALL YOUR STREAM ARE BELONG TO US "
|
|
"stream(%p)=%d\n",
|
|
stream, stream->stream_id));
|
|
|
|
first = stream->roots->head;
|
|
|
|
/* stream must not be include in stream->roots->head list */
|
|
assert(first != stream);
|
|
|
|
if (first) {
|
|
nghttp2_stream *prev;
|
|
|
|
prev = first;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: root stream(%p)=%d\n", first,
|
|
first->stream_id));
|
|
|
|
stream->sum_dep_weight += first->weight;
|
|
stream->num_substreams += first->num_substreams;
|
|
|
|
if (stream->dpri != NGHTTP2_STREAM_DPRI_NO_ITEM &&
|
|
(first->dpri == NGHTTP2_STREAM_DPRI_TOP ||
|
|
(first->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM &&
|
|
first->sum_norest_weight))) {
|
|
stream_update_dep_set_rest(first);
|
|
}
|
|
|
|
for (si = first->root_next; si; si = si->root_next) {
|
|
|
|
assert(si != stream);
|
|
|
|
DEBUGF(
|
|
fprintf(stderr, "stream: root stream(%p)=%d\n", si, si->stream_id));
|
|
|
|
stream->sum_dep_weight += si->weight;
|
|
stream->num_substreams += si->num_substreams;
|
|
|
|
if (stream->dpri != NGHTTP2_STREAM_DPRI_NO_ITEM &&
|
|
(si->dpri == NGHTTP2_STREAM_DPRI_TOP ||
|
|
(si->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM &&
|
|
si->sum_norest_weight))) {
|
|
stream_update_dep_set_rest(si);
|
|
}
|
|
|
|
link_sib(prev, si);
|
|
si->dep_prev = stream;
|
|
|
|
prev = si;
|
|
}
|
|
|
|
if (stream->dep_next) {
|
|
nghttp2_stream *sib_next;
|
|
|
|
sib_next = stream->dep_next;
|
|
|
|
first->dep_prev = stream;
|
|
link_sib(first, sib_next);
|
|
link_dep(stream, prev);
|
|
} else {
|
|
link_dep(stream, first);
|
|
}
|
|
}
|
|
|
|
nghttp2_stream_roots_remove_all(stream->roots);
|
|
|
|
return nghttp2_stream_dep_make_root(stream, session);
|
|
}
|
|
|
|
int nghttp2_stream_in_dep_tree(nghttp2_stream *stream) {
|
|
return stream->dep_prev || stream->dep_next || stream->sib_prev ||
|
|
stream->sib_next || stream->root_next || stream->root_prev ||
|
|
stream->roots->head == stream;
|
|
}
|
|
|
|
void nghttp2_stream_roots_init(nghttp2_stream_roots *roots) {
|
|
roots->head = NULL;
|
|
roots->num_streams = 0;
|
|
}
|
|
|
|
void nghttp2_stream_roots_free(nghttp2_stream_roots *roots _U_) {}
|
|
|
|
void nghttp2_stream_roots_add(nghttp2_stream_roots *roots,
|
|
nghttp2_stream *stream) {
|
|
if (roots->head) {
|
|
stream->root_next = roots->head;
|
|
roots->head->root_prev = stream;
|
|
}
|
|
|
|
roots->head = stream;
|
|
}
|
|
|
|
void nghttp2_stream_roots_remove(nghttp2_stream_roots *roots,
|
|
nghttp2_stream *stream) {
|
|
nghttp2_stream *root_prev, *root_next;
|
|
|
|
root_prev = stream->root_prev;
|
|
root_next = stream->root_next;
|
|
|
|
if (root_prev) {
|
|
root_prev->root_next = root_next;
|
|
|
|
if (root_next) {
|
|
root_next->root_prev = root_prev;
|
|
}
|
|
} else {
|
|
if (root_next) {
|
|
root_next->root_prev = NULL;
|
|
}
|
|
|
|
roots->head = root_next;
|
|
}
|
|
|
|
stream->root_prev = NULL;
|
|
stream->root_next = NULL;
|
|
}
|
|
|
|
void nghttp2_stream_roots_remove_all(nghttp2_stream_roots *roots) {
|
|
nghttp2_stream *si, *next;
|
|
|
|
for (si = roots->head; si;) {
|
|
next = si->root_next;
|
|
|
|
si->root_prev = NULL;
|
|
si->root_next = NULL;
|
|
|
|
si = next;
|
|
}
|
|
|
|
roots->head = NULL;
|
|
}
|