Previously, we only updated stream's weight field when only weight was
changed by PRIORITY frame. If stream is queued, it would be better to
actually reschedule it based on new weight. This could be especially
useful if weight is increased.
Previously, we updated descendant_last_cycle in
nghttp2_stream_reschedule, which is called after non-zero DATA frame.
But this was not optimal since we still had old descendant_last_cycle,
and new stream was scheduled based on it. Now descendant_last_cycle
is updated in nghttp2_stream_next_outbound_item, which is called when
stream with highest priority is selected from queue. And new stream
is scheduled based on it. This commit also removes 0-reset of
descendant_last_cycle and cycle in nghttp2_stream_reschedule. This
could help making them lower, so that they are not overflow. But
there is a pattern that it doesn't work, and we are not sure they are
really useful at this moment.
Because of the nature of heap based priority queue, and our compare
function, streams with the same weight and same parent are handled in
the reverse order they pushed to the queue. To allow stream pushed
earlier to be served first, secondary key "seq" is introduced to break
a tie. "seq" is monotonically increased integer per parent stream,
and it is assigned to stream when it is pused to the queue, and gets
incremented.
When stream is removed from tree, stream is either closed, or its
remote flow control window is depleted. In the latter case, we
schedule this stream as fast as possible if its remote window gets
positive, since it did not sent anything in its turn. To achieve
this, reset last_writelen to 0 when stream is removed from tree.
The intention of this stream API is give server application about
stream dependency information, so that it can utilize it for better
scheduling of stream processing. We have no plan to add object
oriented API based on stream object.
We now use priority queue per stream, which contains the stream which
has ready to send a frame, or one of its descendants have a frame to
send. We maintain invariant that if a stream is queued, then its
ancestors are also queued (except for root). When we re-schedule
stream after transmission, we re-schedule all ancestors, so that
streams on the other path can get a chance to send. This is basically
the same mechanism h2o project uses, but there are differences in the
details.
Previously, the number of stream in one dependency tree (not including
root) is limited to 120. This is due to the fact that we use
recursive calls to traverse trees. Now we replaced recursive calls
with loop, we can remove this limitation. Also now all streams are
descendant of root stream, rather than linked list of individual
subtree root.
After reviewing codebase, only queue for DATA frames requires
priorities. Other frames can be replaced multiple linear queues.
Replacing priority queue with linear queue allows us to simplify
codebase a bit; for example, now nghttp2_session.next_seq is gone.
Previously we did not check HTTP semantics and it is left out for
application. Although checking is relatively easy, but they are
scattered and error prone. We have implemented these checks in our
applications and also feel they are tedious. To make application
development a bit easier, this commit adds basic HTTP semantics
validation to library code. We do following checks:
server:
* HEADERS is either request header or trailer header. Other type of
header is disallowed.
client:
* HEADERS is either zero or more non-final response header or final
response header or trailer header. Other type of header is
disallowed.
For both:
* Check mandatory pseudo header fields.
* Make sure that content-length matches the amount of DATA we
received.
If validation fails, RST_STREAM of type PROTOCOL_ERROR is issued.
Previously we treat stream in NGHTTP2_STREAM_RESERVED state specially,
that is we don't increment or decrement streams counts if stream is in
that state. Because of this, we don't change the stream state to
NGHTTP2_STREAM_CLOSING if stream is in NGHTTP2_STREAM_RESERVED. But
it turns out that it causes a problem. If client canceled pushed
stream before push response HEADERS, stream is still in
NGHTTP2_STREAM_RESERVED state. If push response HEADERS arrived in
this state, library happily accepts it and passed to application.
With this commit, this bug was corrected. We now change stream state
to NGHTTP2_STREAM_CLOSING even if it was in NGHTTP2_STREAM_RESERVED
state. We now use NGHTTP2_STREAM_FLAG_PUSH to determine whether we
have to increase/decrase stream count.
Initially, we use nghttp2_stream.data_item to refer only item with
DATA frame. But recently we use it to refer HEADERS frame as well.
So it is better to call just item rather than data_item. This applies
to all related functions.
We make following HEADERS under priority control:
* push response HEADERS
* HEADERS submitted by nghttp2_submit_response
Currently, HEADERS submitted by nghttp2_submit_headers is not attached
to stream. This is because it may be used as non-final response
header and application may submit final response using
nghttp2_submit_response without checking non-final response header
transmission.
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 also means that at least one stream whose dpri is
NGHTTP2_STREAM_DPRI_TOP exists, its siblings descendants have no
chance to send streams, even if their parent stream has
NGHTTP2_STREAM_DPRI_NODATA.
Previously when nghttp2_stream_resume_deferred_data() is called,
deferred flags in stream->flags are all cleared. This is not ideal
because if application returned NGHTTP2_ERR_DEFERRED, and also that
stream is deferred by flow control, then all flags are cleared and
read callback will be invoked again. This commit fixes this issue.
This changes error condition of nghttp2_session_resume_data().
Previously we return error if stream was deferred by flow control.
Now we don't return error in this case. We just clear
NGHTTP2_FLAG_DEFERRED_USER and if still
NGHTTP2_FLAG_DEFERRED_FLOW_CONTROL is set, just return 0.
Previously we missed the case where stream->data_item is not deleted
and it caused leak. Now stream->data_item is properly deleted when
session is deleted. We decided not to delete data_item in
nghttp2_stream_free() since we need nghttp2_session to decide whether
data_item should be deleted or not there.