New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stream Multiplexing and Flow Control #14
Conversation
415bdb4
to
096e21d
Compare
8350e90
to
34fb69f
Compare
I finally managed to to read through to the end in a single session, sorry that it took so long! So all in all I think this looks fine. The only thing that struck me as odd was the global dictionary used for the stream IDs. If at all possible, this should be using direct pointers/references (GC or RC) instead of going throgh a string lookup (and string construction). Other than that, since the only sensible mode of review in this case is to review the whole code at once anyway, I think it makes sense to squash everything together into just two or three commits (one commit for the added functionality and other commits for fixes to existing code). |
I thought about it (sorry if my response took a while). I think the best overall option would be to use reference counting to efficiently manage the multiplexer struct, so that we don't force GC dependence and don't need to dispose of it explicitly as the TCPConnection gets destroyed (we can probably tie it to the HTTP2ServerContext struct). Only issue with that (and the main reason I choose to adopt a global table as a temporary proof of concept) is that a lot of arguments are passed by copy from one task to the other (see I'll work on the refcount approach and rebase, so that we can proceed. Thank you! |
Rebased and updated in #15. Closing this one. |
This PR proposes a logic for stream multiplexing which should be simple enough to discuss and improve. The changes are focused around the new
multiplexing.d
module, which contains most of the data structures used to maintain the state of a HTTP/2 connection, while enabling the following features:Stream multiplexing
Performed using a table of
HTTP2Multiplexers
which are indexed on a connection-based ID, since the HTTP/2 RFC permits stream multiplexing at connection level. Reference: Section 5, RFC 7540. The process of registering and using a stream multiplexer is the following:http2.d
,handleHTTP2Connection
) a new multiplexer is registered through themultiplexer()
method. This requires an ID for the mux table which must be unique with respect to the connection. This ID is a string generated byconnection.localAddress.toString ~ connection.peerAddress
, whereconnection
is the underlying TCPConnection struct. Since a TCP connection can be uniquely described by the 4 parameters [local host, local port, remote host, remote port] this is sufficient to guarantee that no equal IDs are generated corresponding to two different connections.handleFrameAlloc
registers the new stream by callingregisterStream
with the IDs of the multiplexer and the stream. The HTTP2Multiplexer adds the stream ID to a set of open streams. The stream ID must be unique, to enforce this the set is implemented using a RedBlackTree from phobos.http2.d
,handleFrameAlloc
) until the stream lifetime is over: the stream is then closed by callingcloseStream
, which causes the stream ID to be removed from the set of open streams (for an overview of stream lifetimes, see Section 5.1 of RFC 7540). This process (registering a new stream, handling it, closing it) can be repeated indefinitely on the same HTTP2Multiplexer as long as frames are received and the connection is open.removeMux
with the multiplexer ID.struct HTTP2Multiplexer
embeds the required checks to ensure that the registered streams are valid with respect to the previous one. It does so by keeping track of open stream IDs, the highest open stream ID and checking that clients only provide odd-numbered streams (as specified in RFC 7540). Since we use an asynchronous frame handler (handleHTTP2FrameChain
) the operations which require modifications inside the same HTTP2Multiplexer are protected by a TaskMutex.At the moment, the table of multiplexers is held as a global variable in memory, but I would like to discuss a better/safer approach (even though the handling performed by
multiplexer.d
is working without memory corruption or data races so far).Flow control
Since multiplexing streams in HTTP/2 requires a flow control scheme (see Section 5.2 of RFC 7540)
struct HTTP2Multiplexer
also integrates the information regarding the connection window which the server is allowed to send. The flow control logic works with the following approach:HTTP2Multiplexer
by callingupdateConnectionWindow
andupdateStreamConnectionWindow
.To avoid locking the server while a DATA frame has to be sent, the sending logic for DATA frames is built in an asynchronous fashion in
exchange.d
bysendDataTask()
. The task works by sending all the octets allowed by the connection window, exiting successfully if the DATA payload can be sent completely, otherwise splitting the payload in multiple DATA frames and waiting to send until a new WINDOW_UPDATE is received. During this process, the connection window is progressively updated by subtracting the number of octets sent successfully.Using this logic, the server is able to:
error.d
, in a fashion which is similar to the one used inhttp1.d
, but I agree that there could be more efficient approaches.What is missing:
h2load
benchmarking tool provided by nghttp2. I've also introduces a small web server example inexamples/http2
which should demonstrate basic functionality.@nogc
behavior is something which requires a lot of changes in the way requests are handled and errors are propagated (HTTPServerRequestData, exceptions) but I'm looking into it. IMHO the best moment to discuss that is when the server is stable enough that we can detect changes which break the functionality more easily.This PR spans multiple files since 2 small bugs in HPACK have been corrected, but mainly i because tried to introduce as more documentation as possible, in the form of comments and code refactoring, to ease the review (and the developing) process, I hope it's readable enough.
Feel free to prompt me to split this PR if the changes are too many to keep track of, this PR is meant to describe the progress so far and to agree on a multiplexing strategy.
Thanks!