Subject: MAX_CONCURRENT_STREAMS=0 and PUSH_PROMISE

From: Gábor Molnár <[email-masked]>
Date: Tue, 23 Jul 2013 16:28:31 +0200

It is possible to set SETTINGS_MAX_CONCURRENT_STREAMS to 0. AFAIK, the
design rationale behind this is that a client can forbid server push if it
does not implement it yet or just does not want to receive push streams.

Since streams in "reserved (local)" state do no count as active stream, the
server can still promise push streams even if
SETTINGS_MAX_CONCURRENT_STREAMS is 0. (But it can not actually send the
content on the promised stream.)

This goes against the design rationale, since clients will still have to be
able to deal with PUSH_PROMISEs if they want to comply to the standard.

What is the rationale behind not counting reserved streams as active? They
usually become active very soon so it would make sense to count them as
active as well. This would also prohibit servers promising streams when
SETTINGS_MAX_CONCURRENT_STREAMS is 0.

  Gábor

---

From: Martin Thomson <[email-masked]>
Date: Tue, 23 Jul 2013 09:22:30 -0700

> SETTINGS_MAX_CONCURRENT_STREAMS is 0.

The section on push makes the following note:

A client can use the SETTINGS_MAX_CONCURRENT_STREAMS setting to limit
the number of resources that can be concurrently pushed by a server.
Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables
server push by preventing the server from creating the necessary
streams.

Maybe this needs to be more explicit, as in:

Servers MUST NOT push or promise to push if a client advertises a
value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS.

The rationale for permitting reservation without consuming streams is
to allow a server to push more resources than the concurrent stream
limit would allow.  There are cases where a single page pulls in that
many resources (e.g., image search pages).  Because the associated
stream must remain open until all pushes are complete, the alternative
would be to artificially extend the lifetime of the associated stream,
which compounds complexity.

I know that we briefly discussed having a MAX_PUSH or MAX_RESERVED
setting, but we still don't really have a lot of good information
about what this would mean.

---

From: James M Snell <[email-masked]>
Date: Tue, 23 Jul 2013 09:46:03 -0700

+1 to tightening this up... that is: If
SETTINGS_MAX_CONCURRENT_STREAMS = 0, Servers MUST NOT send
PUSH_PROMISE

---

From: Martin Thomson <[email-masked]>
Date: Tue, 23 Jul 2013 09:57:05 -0700

> SETTINGS_MAX_CONCURRENT_STREAMS = 0, Servers MUST NOT send

https://github.com/http2/http2-spec/issues/180

---

From: Roberto Peon <[email-masked]>
Date: Tue, 23 Jul 2013 10:01:31 -0700

The whole point of push-promise is to not count them, and so to avoid the
condition where the number of resources it wishes to push exceed the max
concurrent stream limit, causingna race.

E.g. if max concurrent stream limit is 10, but I have 21 resources to push.
If push-promise counted, I'd be unable to advertise push promise for all of
these resources, potentially causing a race on the 11 resources the server
was not allowed to send luah-promise for. This would be true even if I was
sending the resources one at a time...

You ask a separate question about whether or not push-promise can be sent
when max-concurrency is 0. Personally, I don't see any real harm in it. The
client can discard the data after putting them through the compressor.
Push-promise is only distinguishable from the headers  frame by the opcode,
so we should be talking about very minimal amounts of code.

-=R
 On Jul 23, 2013 7:30 AM, "Gábor Molnár" <[email-masked]> wrote:

> Since streams in "reserved (local)" state do no count as active stream,
> the server can still promise push streams even if
> This goes against the design rationale, since clients will still have to
> be able to deal with PUSH_PROMISEs if they want to comply to the standard.
>

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 23 Jul 2013 22:04:19 +0300

> concurrent stream limit, causingna race.

How it does cause a race? Stall the associated stream and open some promised
streams if you have slots for it (if not, complete some old open pushed
streams first). No races there.

Or are you talking about client shrinking number of allowed promised frames
mid-connection? Yeah, that sort of thing is fundamentally racy.

> sending the resources one at a time...

Any sensible client (and especially proxy) is going to
RST_STREAM(REFUSED_STREAM) any promised stream over some limit.

Otherwise, have fun with 1,000,000,000 promised streams... And promised
streams are not free for client/proxy.

> so we should be talking about very minimal amounts of code.

I regard that operation as just plain nonsense.

Want to test if client is going to send back RST_STREAM(REFUSED_STREAM) or
GOAWAY(INTERNAL_ERROR)? :-)

-Ilari

---

From: Roberto Peon <[email-masked]>
Date: Tue, 23 Jul 2013 14:32:31 -0700

responses inline

> streams first). No races there.

The client, having no indication that a particular resource will be pushed,
will instead request the resource, and now the server may be pushing a
resource for which it is also responding to the client, solely because the
advertisement about what it was going to push was delayed.
In other words, that is a race if the server ever does intend to push the
resource.

> mid-connection? Yeah, that sort of thing is fundamentally racy.

That would also be racy. Any limit would be racy.
The number of resources in a web page are not limited, thus the number of
signals that the resources will be pushed needs to be symmetrically
unlimited.

> streams are not free for client/proxy.

True, you can get into abusive edge-cases where it is preferable to forget
about the push promise and allow the race.
In such cases, the client would RST the promised stream and request it.
This is a race with very different resultant semantics-- the server won't
be using up the available bandwidth twice at once. The client will know
exactly what it is doing.

> I regard that operation as just plain nonsense.

Then you're not thinking deeply enough yet :)
Even if push promises were limited by some configuration, changes to that
configuration can cause this condition to occur. Thus, clients must be able
to handle PUSH_PROMISE frames by at least discarding them.

> GOAWAY(INTERNAL_ERROR)? :-)

This is something we have to test anyway? :)
-=R

---

From: Martin Thomson <[email-masked]>
Date: Tue, 23 Jul 2013 15:13:00 -0700

> to handle PUSH_PROMISE frames by at least discarding them.

I'm a little concerned by the suggestion that PUSH_PROMISE can simply
be discarded.  It creates a reservation for a stream, which
necessarily creates state in the client.

If, as you point out, the client could lift this limit from zero, is
there some corresponding expectation that the client remembers all of
these reserved streams so that they can enter the right state at the
point that the server decides to exercise the reservation?  Without
any limits on number or the time that a reservation can remain
outstanding, the only rational response a client implementer has is to
reset those streams, if they don't want unbounded state commitment
(even if it is a relatively small allocation for each stream - 2 bits
in the best case - anything greater than zero could be problematic).

We've done a lot to protect resources on a server, but I don't think
that a client is able to limit the commitments it has to make in order
to continue to use a connection.

---

From: Roberto Peon <[email-masked]>
Date: Tue, 23 Jul 2013 15:21:09 -0700

> necessarily creates state in the client.

Well, the alternative is to crash :)
And by discard, I mean RST the reserved stream.
RSTing a reserved stream that the client has no intention of listening to
seems reasonable.
Market forces will force servers to mostly not do stupid crud w.r.t.
sending PUSH_PROMISE when the client really won't use it.
I could imagine smart clients that, wishing to conserve bandwidth, still
want the server to use PUSH_PROMISE like things instead of inlining, but
are happy to leave the potential latency savings on the table.
Such clients want to see what the server is advertising, even when the
number of pushed streams is limited to zero.

Also, remember that a server may push a cache validation/invalidation...

> in the best case - anything greater than zero could be problematic).
I believe the client should remember everything it hasn't RST.

> to continue to use a connection.

Given the above, do you still feel that way? :)
-=R

---

From: Martin Thomson <[email-masked]>
Date: Tue, 23 Jul 2013 15:35:39 -0700

> Given the above, do you still feel that way? :)

You're a server guy.  That mostly just reads like a "it's clients, who
cares, they can deal with it".  I'll hold my opinion until the client
folks reply.

---

From: Roberto Peon <[email-masked]>
Date: Tue, 23 Jul 2013 15:38:02 -0700

I'm a proxy guy, actually.
I have to deal with both sides constantly, except that I'm more memory,
CPU, and bandwidth constrained. It is true that I don't have to deal with
rendering, however.

-=R

---

From: Martin Thomson <[email-masked]>
Date: Tue, 23 Jul 2013 16:43:50 -0700

> I'm a proxy guy, actually.

Everyone writing a stack deals with these problems at some level.
Some will just have to worry about concurrency and queuing at a
different layer, that's all :)

After some offline discussion, I understand that we still (perhaps
grudgingly) need a way to signal that a client is unwilling to receive
pushes.  However, that operates at two levels:
a) a client is unwilling to receive pushes, ever
b) a client is currently unwilling to receive pushes, but will accept
promises (i.e., the requests) in the expectation that it can receive
the corresponding responses in time

The concern here is that setting the maximum concurrent streams to
zero could be used to communicate either situation, and that sometimes
the distinction is important.

I think that onus is on Roberto to more effectively motivate the need
for this distinction.

If indeed we agree that the two cases are distinct, then we probably
need to consider ways to communicate this distinction effectively.  A
separate setting that expressly disables push promise or limits the
number of promises might work.

---

From: Roberto Peon <[email-masked]>
Date: Tue, 23 Jul 2013 17:43:35 -0700

For reasons of DoS prevention, a proxy may wish to throttle the push'd
entity-body data by limiting the concurrency without messing around with
the flow-control (since that would also slow down the main-resource
transfer, unless on separate connections).
As an example DoS attack, a client could start out a connection with push
enabled, then disable it in an attempt to run the proxy out of memory/make
the proxy do useless work.
The proxy may still wish to get the push-promise metadata, however, as it
both indicates what work might have been done (so it can anaylze the effect
of things), and as a first-order effect, allows the proxy to determine what
elements it can serve from cache instead of spending CPU on IO, which is
pretty expensive.
-=R

---

From: Leif Hedstrom <[email-masked]>
Date: Mon, 29 Jul 2013 22:28:08 +0200

> number of promises might work.

I'm very much in favor of this, be explicit about the client option to 
tell the server to never send push promises. It avoids any potential 
semantics overloading issues that we haven't yet foreseen. Plus, Roberto 
makes some pretty compelling arguments as to why it can make sense for a 
client to set MAX_CONCURRENT_STREAMS=0 but still want to see the push 
promises.

-- Leif

---

From: Gábor Molnár <[email-masked]>
Date: Tue, 30 Jul 2013 10:26:11 +0200

I'm OK with either solution (forbid push when max_streams=0 or signal
difference between two situations). From an implementation
perspective, I don't think many public server would support sending
PUSH_PROMISEs when they cannot push the actual content.

2013/7/29 Leif Hedstrom <[email-masked]>:
> I'm very much in favor of this, be explicit about the client option to tell
> the server to never send push promises. It avoids any potential semantics
> overloading issues that we haven't yet foreseen. Plus, Roberto makes some
> pretty compelling arguments as to why it can make sense for a client to set
> MAX_CONCURRENT_STREAMS=0 but still want to see the push promises.
>
> -- Leif