1067 lines
28 KiB
C
1067 lines
28 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->effective_weight = stream->weight;
|
|
stream->sum_dep_weight = 0;
|
|
stream->sum_norest_weight = 0;
|
|
stream->sum_top_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;
|
|
}
|
|
|
|
if (item->weight > stream->effective_weight) {
|
|
item->weight = stream->effective_weight;
|
|
}
|
|
|
|
item->cycle = session->last_cycle;
|
|
|
|
switch (item->frame.hd.type) {
|
|
case NGHTTP2_DATA:
|
|
rv = nghttp2_pq_push(&session->ob_da_pq, item);
|
|
break;
|
|
case NGHTTP2_HEADERS:
|
|
if (stream->state == NGHTTP2_STREAM_RESERVED) {
|
|
rv = nghttp2_pq_push(&session->ob_ss_pq, item);
|
|
} else {
|
|
rv = nghttp2_pq_push(&session->ob_pq, item);
|
|
}
|
|
break;
|
|
default:
|
|
/* should not reach here */
|
|
assert(0);
|
|
}
|
|
|
|
if (rv != 0) {
|
|
return rv;
|
|
}
|
|
|
|
item->queued = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static nghttp2_stream *stream_first_sib(nghttp2_stream *stream) {
|
|
for (; stream->sib_prev; stream = stream->sib_prev)
|
|
;
|
|
|
|
return stream;
|
|
}
|
|
|
|
static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
|
|
for (; stream->sib_next; stream = stream->sib_next)
|
|
;
|
|
|
|
return stream;
|
|
}
|
|
|
|
static nghttp2_stream *stream_update_dep_length(nghttp2_stream *stream,
|
|
ssize_t delta) {
|
|
stream->num_substreams += delta;
|
|
|
|
stream = stream_first_sib(stream);
|
|
|
|
if (stream->dep_prev) {
|
|
return stream_update_dep_length(stream->dep_prev, delta);
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int32_t nghttp2_stream_dep_distributed_effective_weight(nghttp2_stream *stream,
|
|
int32_t weight) {
|
|
if (stream->sum_norest_weight == 0) {
|
|
return stream->effective_weight;
|
|
}
|
|
|
|
weight = stream->effective_weight * weight / stream->sum_norest_weight;
|
|
|
|
return nghttp2_max(1, weight);
|
|
}
|
|
|
|
static int32_t
|
|
stream_dep_distributed_top_effective_weight(nghttp2_stream *stream,
|
|
int32_t weight) {
|
|
if (stream->sum_top_weight == 0) {
|
|
return stream->effective_weight;
|
|
}
|
|
|
|
weight = stream->effective_weight * weight / stream->sum_top_weight;
|
|
|
|
return nghttp2_max(1, weight);
|
|
}
|
|
|
|
static void stream_update_dep_set_rest(nghttp2_stream *stream);
|
|
|
|
/* Updates effective_weight of descendant streams in subtree of
|
|
|stream|. We assume that stream->effective_weight is already set
|
|
right. */
|
|
static void stream_update_dep_effective_weight(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: update_dep_effective_weight "
|
|
"stream(%p)=%d, weight=%d, sum_norest_weight=%d, "
|
|
"sum_top_weight=%d\n",
|
|
stream, stream->stream_id, stream->weight,
|
|
stream->sum_norest_weight, stream->sum_top_weight));
|
|
|
|
/* stream->sum_norest_weight == 0 means there is no
|
|
NGHTTP2_STREAM_DPRI_TOP under stream */
|
|
if (stream->dpri != NGHTTP2_STREAM_DPRI_NO_ITEM ||
|
|
stream->sum_norest_weight == 0) {
|
|
return;
|
|
}
|
|
|
|
/* If there is no direct descendant whose dpri is
|
|
NGHTTP2_STREAM_DPRI_TOP, indirect descendants have the chance to
|
|
send data, so recursively set weight for descendants. */
|
|
if (stream->sum_top_weight == 0) {
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
if (si->dpri != NGHTTP2_STREAM_DPRI_REST) {
|
|
si->effective_weight =
|
|
nghttp2_stream_dep_distributed_effective_weight(stream, si->weight);
|
|
}
|
|
|
|
stream_update_dep_effective_weight(si);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* If there is at least one direct descendant whose dpri is
|
|
NGHTTP2_STREAM_DPRI_TOP, we won't give a chance to indirect
|
|
descendants, since closed or blocked stream's weight is
|
|
distributed among its siblings */
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
if (si->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
si->effective_weight =
|
|
stream_dep_distributed_top_effective_weight(stream, si->weight);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d top eweight=%d\n",
|
|
si->stream_id, si->effective_weight));
|
|
|
|
continue;
|
|
}
|
|
|
|
if (si->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM) {
|
|
DEBUGF(fprintf(stderr, "stream: stream=%d no_item, ignored\n",
|
|
si->stream_id));
|
|
|
|
/* Since we marked NGHTTP2_STREAM_DPRI_TOP under si, we make
|
|
them NGHTTP2_STREAM_DPRI_REST again. */
|
|
stream_update_dep_set_rest(si->dep_next);
|
|
} else {
|
|
DEBUGF(
|
|
fprintf(stderr, "stream: stream=%d rest, ignored\n", si->stream_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void stream_update_dep_set_rest(nghttp2_stream *stream) {
|
|
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;
|
|
|
|
stream_update_dep_set_rest(stream->sib_next);
|
|
|
|
return;
|
|
}
|
|
|
|
stream_update_dep_set_rest(stream->sib_next);
|
|
stream_update_dep_set_rest(stream->dep_next);
|
|
}
|
|
|
|
/*
|
|
* Performs dfs starting |stream|, search stream which can become
|
|
* NGHTTP2_STREAM_DPRI_TOP and set its dpri.
|
|
*/
|
|
static void stream_update_dep_set_top(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
stream_update_dep_set_top(si);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
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 and stream->sum_top_weight
|
|
* recursively. We have to gather effective sum of weight of
|
|
* descendants. If stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM, we
|
|
* have to go deeper and check that any of its descendants has dpri
|
|
* value of NGHTTP2_STREAM_DPRI_TOP. If so, we have to add weight of
|
|
* its direct descendants to stream->sum_norest_weight. To make this
|
|
* work, this function returns 1 if any of its descendants has dpri
|
|
* value of NGHTTP2_STREAM_DPRI_TOP, otherwise 0.
|
|
*
|
|
* Calculating stream->sum_top-weight is very simple compared to
|
|
* stream->sum_norest_weight. It just adds up the weight of direct
|
|
* descendants whose dpri is NGHTTP2_STREAM_DPRI_TOP.
|
|
*/
|
|
static int stream_update_dep_sum_norest_weight(nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
int rv;
|
|
|
|
stream->sum_norest_weight = 0;
|
|
stream->sum_top_weight = 0;
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
return 1;
|
|
}
|
|
|
|
if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
|
|
return 0;
|
|
}
|
|
|
|
rv = 0;
|
|
|
|
for (si = stream->dep_next; si; si = si->sib_next) {
|
|
|
|
if (stream_update_dep_sum_norest_weight(si)) {
|
|
rv = 1;
|
|
stream->sum_norest_weight += si->weight;
|
|
}
|
|
|
|
if (si->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
|
stream->sum_top_weight += si->weight;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int stream_update_dep_on_attach_item(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *root_stream;
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_REST;
|
|
|
|
stream_update_dep_set_rest(stream->dep_next);
|
|
|
|
root_stream = nghttp2_stream_get_dep_root(stream);
|
|
|
|
DEBUGF(fprintf(stderr, "root=%p, stream=%p\n", root_stream, stream));
|
|
|
|
stream_update_dep_set_top(root_stream);
|
|
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
|
|
return stream_update_dep_queue_top(root_stream, session);
|
|
}
|
|
|
|
static int stream_update_dep_on_detach_item(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *root_stream;
|
|
|
|
stream->dpri = NGHTTP2_STREAM_DPRI_NO_ITEM;
|
|
|
|
root_stream = nghttp2_stream_get_dep_root(stream);
|
|
|
|
stream_update_dep_set_top(root_stream);
|
|
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
|
|
return stream_update_dep_queue_top(root_stream, session);
|
|
}
|
|
|
|
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 (;;) {
|
|
if (stream->sib_prev) {
|
|
stream = stream->sib_prev;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (stream->dep_prev) {
|
|
stream = stream->dep_prev;
|
|
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void nghttp2_stream_dep_insert(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream) {
|
|
nghttp2_stream *si;
|
|
nghttp2_stream *root_stream;
|
|
|
|
assert(stream->item == NULL);
|
|
|
|
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;
|
|
|
|
if (dep_stream->dep_next) {
|
|
for (si = dep_stream->dep_next; si; si = si->sib_next) {
|
|
stream->num_substreams += si->num_substreams;
|
|
}
|
|
|
|
stream->dep_next = dep_stream->dep_next;
|
|
stream->dep_next->dep_prev = stream;
|
|
}
|
|
|
|
dep_stream->dep_next = stream;
|
|
stream->dep_prev = dep_stream;
|
|
|
|
root_stream = stream_update_dep_length(dep_stream, 1);
|
|
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
|
|
++stream->roots->num_streams;
|
|
}
|
|
|
|
static void link_dep(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
|
dep_stream->dep_next = stream;
|
|
stream->dep_prev = dep_stream;
|
|
}
|
|
|
|
static void link_sib(nghttp2_stream *prev_stream, nghttp2_stream *stream) {
|
|
prev_stream->sib_next = stream;
|
|
stream->sib_prev = prev_stream;
|
|
}
|
|
|
|
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);
|
|
|
|
sib_next->dep_prev = NULL;
|
|
|
|
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
|
|
*/
|
|
dep_next->dep_prev = NULL;
|
|
|
|
link_sib(prev, dep_next);
|
|
|
|
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);
|
|
|
|
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) {
|
|
nghttp2_stream *root_stream;
|
|
|
|
assert(stream->item == NULL);
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_add dep_stream(%p)=%d, stream(%p)=%d\n",
|
|
dep_stream, dep_stream->stream_id, stream, stream->stream_id));
|
|
|
|
root_stream = 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_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
|
|
++stream->roots->num_streams;
|
|
}
|
|
|
|
void nghttp2_stream_dep_remove(nghttp2_stream *stream) {
|
|
nghttp2_stream *prev, *next, *dep_prev, *si, *root_stream;
|
|
int32_t sum_dep_weight_delta;
|
|
|
|
root_stream = NULL;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_remove stream(%p)=%d\n", stream,
|
|
stream->stream_id));
|
|
|
|
/* Distribute weight of |stream| to direct descendants */
|
|
sum_dep_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;
|
|
}
|
|
|
|
prev = stream_first_sib(stream);
|
|
|
|
dep_prev = prev->dep_prev;
|
|
|
|
if (dep_prev) {
|
|
root_stream = stream_update_dep_length(dep_prev, -1);
|
|
|
|
dep_prev->sum_dep_weight += sum_dep_weight_delta;
|
|
}
|
|
|
|
if (stream->sib_prev) {
|
|
unlink_sib(stream);
|
|
} else if (stream->dep_prev) {
|
|
unlink_dep(stream);
|
|
} 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;
|
|
|
|
/* We already distributed weight of |stream| to this. */
|
|
si->effective_weight = si->weight;
|
|
|
|
nghttp2_stream_roots_add(si->roots, si);
|
|
|
|
si = next;
|
|
}
|
|
}
|
|
|
|
if (root_stream) {
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
}
|
|
|
|
stream->num_substreams = 1;
|
|
stream->sum_dep_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 *root_stream;
|
|
size_t delta_substreams;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
stream_update_dep_set_rest(dep_next);
|
|
|
|
link_dep(dep_stream, stream);
|
|
|
|
if (stream->dep_next) {
|
|
last_sib = stream_last_sib(stream->dep_next);
|
|
|
|
link_sib(last_sib, dep_next);
|
|
|
|
dep_next->dep_prev = NULL;
|
|
} else {
|
|
link_dep(stream, dep_next);
|
|
}
|
|
} else {
|
|
link_dep(dep_stream, stream);
|
|
|
|
assert(dep_stream->sum_dep_weight == 0);
|
|
dep_stream->sum_dep_weight = stream->weight;
|
|
}
|
|
|
|
root_stream = stream_update_dep_length(dep_stream, delta_substreams);
|
|
|
|
stream_update_dep_set_top(root_stream);
|
|
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
|
|
return stream_update_dep_queue_top(root_stream, session);
|
|
}
|
|
|
|
int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream,
|
|
nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
nghttp2_stream *root_stream;
|
|
|
|
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));
|
|
|
|
stream_update_dep_set_rest(stream);
|
|
|
|
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;
|
|
}
|
|
|
|
root_stream = stream_update_dep_length(dep_stream, stream->num_substreams);
|
|
|
|
stream_update_dep_set_top(root_stream);
|
|
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
|
|
return stream_update_dep_queue_top(root_stream, session);
|
|
}
|
|
|
|
void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream) {
|
|
nghttp2_stream *prev, *next, *dep_prev, *root_stream;
|
|
|
|
DEBUGF(fprintf(stderr, "stream: dep_remove_subtree stream(%p)=%d\n", stream,
|
|
stream->stream_id));
|
|
|
|
if (stream->sib_prev) {
|
|
prev = stream->sib_prev;
|
|
|
|
prev->sib_next = stream->sib_next;
|
|
if (prev->sib_next) {
|
|
prev->sib_next->sib_prev = prev;
|
|
}
|
|
|
|
prev = stream_first_sib(prev);
|
|
|
|
dep_prev = prev->dep_prev;
|
|
|
|
} else if (stream->dep_prev) {
|
|
dep_prev = stream->dep_prev;
|
|
next = stream->sib_next;
|
|
|
|
dep_prev->dep_next = next;
|
|
|
|
if (next) {
|
|
next->dep_prev = dep_prev;
|
|
|
|
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;
|
|
|
|
root_stream = stream_update_dep_length(dep_prev, -stream->num_substreams);
|
|
|
|
stream_update_dep_sum_norest_weight(root_stream);
|
|
stream_update_dep_effective_weight(root_stream);
|
|
}
|
|
|
|
stream->sib_prev = NULL;
|
|
stream->sib_next = NULL;
|
|
stream->dep_prev = NULL;
|
|
}
|
|
|
|
int nghttp2_stream_dep_make_root(nghttp2_stream *stream,
|
|
nghttp2_session *session) {
|
|
DEBUGF(fprintf(stderr, "stream: dep_make_root stream(%p)=%d\n", stream,
|
|
stream->stream_id));
|
|
|
|
nghttp2_stream_roots_add(stream->roots, stream);
|
|
|
|
stream_update_dep_set_rest(stream);
|
|
|
|
stream->effective_weight = stream->weight;
|
|
|
|
stream_update_dep_set_top(stream);
|
|
|
|
stream_update_dep_sum_norest_weight(stream);
|
|
stream_update_dep_effective_weight(stream);
|
|
|
|
return stream_update_dep_queue_top(stream, session);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
link_sib(prev, si);
|
|
|
|
prev = si;
|
|
}
|
|
|
|
if (stream->dep_next) {
|
|
nghttp2_stream *sib_next;
|
|
|
|
sib_next = stream->dep_next;
|
|
|
|
sib_next->dep_prev = NULL;
|
|
|
|
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;
|
|
}
|