Re: Design Issue: Max Concurrent Streams Limit and Unidirectional Streams

On Fri, Apr 26, 2013 at 3:30 AM, RUELLAN Herve
<Herve.Ruellan@crf.canon.fr> wrote:
> I've got the feeling that we should limit in some way the number of promised streams: too many promised streams could hurt an intermediary.
>

The more I think it over (sleeping on the problem helped) the more I
think that the idea of redefining SETTINGS_MAX_CONCURRENT_STREAMS and
adding a separate SETTINGS_OPEN_STREAMS_CREDIT makes the most sense
here. It effectives gives us two distinct and flexible control points.

SETTINGS_MAX_CONCURRENT_STREAMS would set an upper limit on the number
of outbound open streams an endpoint can have at any given time.
SETTINGS_OPEN_STREAMS_CREDIT provides an upper limit on the total
number of open streams (or half-open in any direction) an endpoint can
initiate. Rather than being a hard limit, however, the recipient, if
it wishes, can allow the sender to overdraw on its credit. If the
recipient is concerned that the sender is misbehaving, flow control
mechanism can be used by the recipient to tell the sender to slow down
or stop, or it can simply choose to reject all new streams.

Example 1: Client to Sender

Client opens the connection, Server tells the client that they have a
MAX_CONCURRENT_STREAMS of 10, OPEN_STREAMS_CREDIT of 20. This means
that the browser cannot initiate more than 10 open streams at a time.
If it creates 10, then half-closes 5, it can open another five
streams. It's OPEN_STREAMS_CREDIT counter would then be at 15 until
the server half-closes items on it's end. Let's say the client
half-closes five more streams and opens another five. It's still
within it's MAX_CONCURRENT_STREAMS limit, but if the server has not
half-closed any of those streams, the client will have reached it's
OPEN_STREAMS_CREDIT limit. The recipient now has a choice: A) it can
choose to allow the client to continue opening streams, B) it can
choose to use flow-control to tell the client to chill out and slow
down, C) it can hold off on responding to newly initiated stream until
the server has had a chance to half close, or D) it can reject the new
streams with an error. If the client continues to overdraw on it's
open streams credit limit, the recipient can choose to take more
drastic action such as terminating the session.

Example 2: Sender to Client

Client opens the connection and opens a stream to the server. The
client tells the server that it has a MAX_CONCURRENT_STREAMS of 10,
and an OPEN_STREAMS_CREDIT of 20. The server sends 10 PUSH_PROMISES to
the client. These count towards the MAX_CONCURRENT_STREAMS limit and
the OPEN_STREAMS_CREDIT. Once the server half-closes a pushed stream,
it can send another PUSH_PROMISE, but unless the recipient
half-closes, it still counts against their open streams credit limit.
Once that limit is reached, the recipient has the same choices as
above.

Yes, there is a denial of service risk here, but it's no worse than
what we currently have.

- James


> One way to do this is to say that a PUSH_PROMISE creates a new stream. In this way it is counted against the SETTINGS_MAX_CONCURRENT_STREAMS. We would also have to mandate a client to half-close all PUSH_PROMISE streams.
>
>
> While reading the current spec, my understanding on whether a PUSH_PROMISE created a new stream wandered back and forth, and I think some clarification could be useful.
>
> In particular the first paragraph of 3.4.1:
> "There is no coordination or shared action between the client and server required to create a stream. Rather, new streams are established by sending a frame that references a previously unused stream identifier."
>
> The "Promised-Stream-ID" of the PUSH_PROMISE frame could be considered as being the reference to an unused stream identifier, therefore creating the stream.
>
> The rest of 3.4.1 and 3.8.5 on the contrary say that a PUSH_PROMISE frame doesn't create a new stream.
>
> I would propose the following edit to this first paragraph of 3.4.1:
> "There is no coordination or shared action between the client and server required to create a stream. Rather, new streams are established by sending a frame that references in its "Stream Identifier" field a previously unused stream identifier."
>
> Hervé.
>
>> -----Original Message-----
>> From: Martin Thomson [mailto:martin.thomson@gmail.com]
>> Sent: jeudi 25 avril 2013 21:26
>> To: James M Snell
>> Cc: ietf-http-wg@w3.org
>> Subject: Re: Design Issue: Max Concurrent Streams Limit and Unidirectional
>> Streams
>>
>> On 25 April 2013 10:50, James M Snell <jasnell@gmail.com> wrote:
>> > https://github.com/http2/http2-spec/issues/78
>> >...
>> > If a client sets a limit of 4 concurrent streams, and the server
>> >initiates 4 separate PUSH_PROMISE streams that the server half-closes
>> >but that are never half-closed by the client, the server will not be
>> >able to initiate new push streams for the duration of the session.
>>
>> Yep, it's a problem.  We got rid of the unidirectional flag that addressed this.  I
>> can't speak for others, but I was aware of the issue at the time, but I had a
>> solution in mind.  That never got written down, partly because we didn't
>> have this discussion :)
>>
>> On first blush, the only way to avoid the problem is to expect the framing
>> layer to be aware of what is going on above, but that's probably not sensible.
>> But there's a better way:
>>
>> Each stream has two separate state variables, each with three state
>> values: no packet yet, open, half-closed.  Streams that have inbound ==
>> open || outbound == open are "in use" and count toward the stream limit.
>> Documenting this might help clarify how the accounting is done.
>>
>> Importantly, this means that promised streams do not count toward the
>> limit.  It does however also imply that implementations will need to be
>> careful about how they allocate stream resources.  Pushes complicate that a
>> little because the lifecycle of headers doesn't match stream lifecycles.  Again,
>> I'd suggest an approach where implementations defer commitment of flow
>> control buffers until the first flow-controlled frame arrives (memory pre-
>> allocation might be advisable for performance reasons, but that would not be
>> an actual
>> commitment) and to ensure that any state for send and receive don't have
>> the same lifecycle.
>

Received on Friday, 26 April 2013 16:03:39 UTC