Subject: [quicwg/base-drafts] Concurrent Streams (#263)

From: Marten Seemann <[email-masked]>
Date: Sat, 04 Feb 2017 22:43:39 -0800

This is an issue we briefly discussed in a coffee break in Tokyo, that nobody has written up so far.

We currently don't have a definition of the number of concurrent streams that both peers agree on at every given moment. Chrome and quic-go solve this problem by allowing a higher number of open streams (10% or 10 streams more, whichever is higher, than the advertised limit). This seems to work fine for HTTP, but will probably break down when using many short-lived streams.

### Problem analysis

The main problem here is that it is very difficult for the sender to determine if the receiver considers a stream closed at any given moment. To do so, it would have to keep track of all packets containing stream data for that stream. Only when all of those packets have been ACKed (or retransmissions of those packets, if any packets were lost), it knows that the stream changed its state on the receiver's side.

I think there's a more fundamental problem hidden here: Receiving a FIN should not cause a state transition in the first place. The concurrent stream limit is supposed to protect a receiver from allocating to many resources. Therefore, a stream should only be considered closed once the application finished reading from that stream (which, if it is reading the whole stream data, is only possible after receiving the FIN, but might be an arbitrary amount of time later, if the application is reading slowly).

### Possible solution

There needs to be a way for the receiver to tell the sender that it considers a stream closed. We could introduce a CLOSED_STREAM frame type for that.
A peer would send a CLOSED_STREAM frame only after it closed the stream locally (i.e. only from the *half closed (locally)* state) and after it finished reading from the stream. 
A peer receiving a CLOSED_STREAM can immediately remove the corresponding stream from the list of open streams.

I can work out the details and provide a PR for this, but I'd like to discuss the proposal here first. It might make sense to finish the changes regarding the resetting of streams (related issues: #165 and #171), since this will already significantly change the stream state diagram.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/263

---

From: Aron-Schats <[email-masked]>
Date: Sun, 05 Feb 2017 21:13:44 -0800

On Sun, Feb 5, 2017 at 1:43 AM, Marten Seemann <[email-masked]>
wrote:

> The main problem here is that it is very difficult for the sender to
> determine if the receiver considers a stream closed at any given moment. To
> do so, it would have to keep track of all packets containing stream data
> for that stream. Only when all of those packets have been ACKed (or
> retransmissions of those packets, if any packets were lost), it knows that
> the stream changed its state on the receiver's side.
>
So what's the problem?  Keep track of ACKs per stream.

  Aron.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/263#issuecomment-277588392

---

From: Martin Thomson <[email-masked]>
Date: Sun, 05 Feb 2017 22:10:34 -0800

@Aron-Schats, if that were sufficient, you would also need to track an ACK of RST_STREAM in the case where the other side was already closed when you sent a RST_STREAM.

But that's not the issue @marten-seemann is talking about here.  He is asserting that the critical action on the receiver for releasing a stream is not acknowledgment of the packets, but the analog of opening the flow control window.  If a remote end sends data on a stream, then that data counts against the stream flow control budget until a WINDOW_UPDATE is sent.  However, as soon as the stream closes, that flow control budget is now void and the data only counts against the connection-level budget.

The problem here - if you accept that it is a problem - is that the application consuming that data might not finish consuming that data until some time after the stream is detected as "closed".  Any application-level resources allocated to that stream will still be occupied.  Applications (and application protocols) that require resource commitments on a per-stream basis now have to deal with the possibility that there are an unbounded number of streams they need to handle.

HTTP/2 has the same basic problem for server push streams.  In that case, it's probably worse because you don't have to worry about an acknowledgment before the stream is released.  In h2, the server can PUSH_PROMISE infinite times, and as long as the connection throughput permits it, fulfill every promise.  The only real backpressure in h2 is the TCP receive window.  The connection-level flow control only has an effect if there are bodies on the promised responses.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/263#issuecomment-277594256

---

From: ianswett <[email-masked]>
Date: Mon, 06 Feb 2017 13:15:47 -0800

The amount of state necessary to track streams that have been completely read, but are partially buffered doesn't seem that large compared to the amount of data in the stream, and the data in the stream would continue to consume flow control.

This is a particular problem if both the data takes a long time to consume(ie: a slow backend) and streams are tiny, so the amount of state per stream starts adding up.

This would seem to indicate a type of worst case scenario is HTTP GETs from clients.  However, those streams don't get closed until the server closes it's side of the stream, which presumably wouldn't happen in this case.  I believe the fact that both parties need to close the stream to allow it to be reused solves this in practice.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/263#issuecomment-277815742

---

From: Martin Thomson <[email-masked]>
Date: Mon, 06 Feb 2017 20:24:53 -0800

@ianswett, I'm not sure what you mean when you say "partially buffered".  I would caution about saying things like "doesn't seem that large" without knowing precisely what that state is though.  I know that at least in a browser, the state we generate for a response likely far exceeds the size of the bytes transferred.  And I can't think that browsers are special in any way here.

As you say, this isn't a problem for servers responding to GET requests.  At the server, those are strictly limited by the server concurrent stream limit.  The server is never over-committed.  Where it is a problem is at the side that receives the second close.  So clients are more likely to be affected, given that they tend to close first.

It's certainly an argument for keeping the requirement for the "pointless" FIN on server push streams.  At least that way clients can ensure that there is an upper bound on the number of active pushes.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/263#issuecomment-277898100

---

From: ianswett <[email-masked]>
Date: Tue, 07 Feb 2017 07:52:05 -0800

When I say partially buffered, I'm thinking of streams that have all their data delivered, but the thing consuming that data has not finished consuming it.  In the case of servers, that's a slow backend.

To focus on the client side, this would specifically be a problem for streams which are opened by the server, and are never truly open on the client side, correct?  But the client needs to send a "pointless" FIN to properly close it's side? 

If so, I think we're ok because one of two cases is always true:
 1) Locally initiated: Solution is to not open more streams.
 2) Remotely initiated: Don't send a FIN until you're willing to deal with more streams.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/issues/263#issuecomment-278041662