Subject: [quicwg/base-drafts] Why are there two ways of associating push with requests? (#3275)

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Nov 2019 03:39:49 -0800

At the moment, the HTTP draft defines two ways of associating a pushed response with requests. One of the requests being associated uses PUSH_PROMISE, while other requests use DUPLICATE_PUSH when referring to the same pushed response.

This seems like an unnecessary complexity to me.

I wonder if we can use the following design instead:
* Send the *request of a push* on the "push stream," instead of sending it using PUSH_PROMISE.
* Merge PUSH_PROMISE and DUPLICATE_PUSH into one frame. The unified frame type would carry the PUSH_ID only.
* (Optionally) change PUSH_ID to stream ID.

I think that such change would simplify the client-side code, as there would no longer be two different logic for associating a push with a request. On the server-side, the change would also simplify the code, assuming that the server uses DUPLICATE_PUSH. My intuition that servers should be using that to avoid pushing the same resource twice on a single connection.

The argument against making such change would be that there would be no guarantee that the pushed URL would be available to the client before the client discovers the use of that pushed URL. However, in the current state, that guarantee exists only when the PUSH_PROMISE frame is being used, but not when DUPLICATE_PUSH is used. Considering that, I am doubtful if having something that *occasionally* works is worth the complexity.

-- 
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/3275

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Nov 2019 03:55:36 -0800

Note also that the current design has the priority inversion issue.

When the server sends a PUSH_PROMISE frame on request stream A, then sends a DUPLICATE_PUSH frame on stream B referring to the same pushed resource, and if stream B has higher precedence than stream A, then delivery of the request header of the pushed resource might get significantly delayed. Such a behavior would have negative effect on a well-designed client that blocks on the DUPLICATE_PUSH frame until it receives the request header of the pushed resource.

Sending the request headers on the pushed stream resolves the issue. The server can more easily control the priority of a pushed stream, it becomes much easier to send the request headers of a pushed response before anything else.

-- 
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/3275#issuecomment-559056999

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 03:55:59 -0800

> Send the request of a push on the "push stream," instead of sending it using PUSH_PROMISE.

How would you do this? With a HEADERS frame, or some new frame type? The reason I ask is because it would seem to change the message ordering that we have defined today (HEADERS, DATA, HEADERS)

-- 
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/3275#issuecomment-559057119

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Nov 2019 04:04:27 -0800

@LPardue Good point. It can be a new frame, or I think it can also be a HEADERS frame.

In case of the latter, the first HEADERS frame would be the request, and the second HEADERS frame would be the response headers. This can be guaranteed since a push request is never accompanied by a request body.

-- 
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/3275#issuecomment-559059633

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 05:09:54 -0800

Since you mention priority, the other question I have is at what priority is the pushed response delivered at? For example, with the proposal, the server gets request A (low precedence) and B (high precedence) it responds with unified PUSH_PROMISE on both A and B, which priority does the client believe the server is delivering the push stream with?

-- 
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/3275#issuecomment-559080703

---

From: Dmitri Tikhonov <[email-masked]>
Date: Wed, 27 Nov 2019 05:54:38 -0800

Having implemented DUPLICATE_PUSH on the server, I say that the complexity is increased compared with gQUIC or HTTP/2, but it's not too bad.

On the client side, the biggest problem I haven't yet solved is that push promise, when received, might not have a stream associated with it.  At that point, the user code cannot be given a push promise stream to process or refuse the push promise.  This can be solved in a few ways:
- delay informing the user that push promise has arrived until the push stream gets created;
- create a stream but delay assigning stream ID to it (and disable some stream functionality); or
- create a different API for HTTP/3 push promises.

I haven't made up my mind about which of these I dislike the least.

@kazuho's proposal would solve this problem on the client and so I agree with his argumentation regarding client complexity.  Nevertheless, the current design must have some rationale behind it, which I expect @MikeBishop to share with us on this thread.

We should also keep in mind that the HTTP draft entered Late Stage yesterday.

-- 
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/3275#issuecomment-559096810

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 06:40:04 -0800

> On the client side, the biggest problem I haven't yet solved is that push promise, when received, might not have a stream associated with it. At that point, the user code cannot be given a push promise stream to process or refuse the push promise.

Caveat that I have only a very naive API for client-side push handling. I thought one purpose of the current design was to allow the proactive cancellation of pushes before the server committed stream resources, via CANCEL_PUSH.

@kazuho's proposal makes CANCEL_PUSH pretty worthless for the client (and it was already only applicable for a small time window). With this proposal the client needs to decide to cancel based on the limited information in the simplified PUSH_PROMISE. To further simplifies server push just restrict CANCEL_PUSH to be server-sent only and state that a client can only cancel pushes by issuing STOP_SENDING.

-- 
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/3275#issuecomment-559114513

---

From: Dmitri Tikhonov <[email-masked]>
Date: Wed, 27 Nov 2019 06:49:15 -0800

> ... one purpose of the current design was to allow the proactive cancellation of pushes before the server committed stream resources, via CANCEL_PUSH.

Good point, I've forgotten about this.

-- 
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/3275#issuecomment-559118248

---

From: Ryan Hamilton <[email-masked]>
Date: Wed, 27 Nov 2019 12:26:23 -0800

> Send the request of a push on the "push stream," instead of sending it using PUSH_PROMISE.

Would that mean the that URL of the pushed resource would not be guaranteed to known to the client before a reference to that resource was referenced by the body of the main response?

-- 
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/3275#issuecomment-559239880

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 12:27:43 -0800

By my understanding that woul be the case

-- 
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/3275#issuecomment-559240342

---

From: Ryan Hamilton <[email-masked]>
Date: Wed, 27 Nov 2019 12:34:13 -0800

That seems like a show-stopper to me. Push is full of sharp edges already and this would add another one. I think we need a guarantee that if the server is pushing a URL, the client knows about it before it makes a request for that resource.

-- 
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/3275#issuecomment-559242203

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Nov 2019 12:41:11 -0800

> I think we need a guarantee that if the server is pushing a URL, the client knows about it before it makes a request for that resource.

My point is that there is already no such guarantee. A client might receive a DUPLICATE_PUSH with an yet unknown PUSH_ID. In such case, the client cannot tell the URL being pushed.

-- 
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/3275#issuecomment-559244063

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 12:48:56 -0800

Maybe we need something more like the gQUIC design that puts all pushes on a different stream ;)

-- 
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/3275#issuecomment-559246166

---

From: Ryan Hamilton <[email-masked]>
Date: Wed, 27 Nov 2019 13:04:28 -0800

@kazuho Indeed! This is a problem for the DUPLICATE_PUSH case, but not a problem in the simple PUSH_PROMISE case. I don't like the idea of extending this problem. (And I really hate that this is a problem with DUPLICATE_PUSH at all :>).

I wonder if an alternative might be to change DUPLICATE_PUSH to include the URL of the resource being promised? (This won't be the full request headers, but at least it would include the URL).

-- 
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/3275#issuecomment-559250305

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 13:18:37 -0800

I had a similar thought, but then you're on a slippery slope towards sending the complete pseudo-request and back to the problem we had with coordinationating different promises with the same push ID that led to DUPLICATE_PUSH

-- 
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/3275#issuecomment-559254067

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Nov 2019 13:27:11 -0800

@RyanAtGoogle @LPardue I actually think that sending PUSH_PROMISE with the entire set of request headers *on all request streams that refer to the same push* would be a reasonable approach. The client can then check that the request headers are equivalent.

Doing so also solves HoLB caused by priority inversion (see https://github.com/quicwg/base-drafts/issues/3275#issuecomment-559056999).

I do not anticipate that the there would be many request headers within a push, so sending all the request headers would not be a big concern regarding bandwidth.

-- 
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/3275#issuecomment-559256459

---

From: Lucas Pardue <[email-masked]>
Date: Wed, 27 Nov 2019 13:31:50 -0800

That takes us to revisiting https://github.com/quicwg/base-drafts/issues/1864

-- 
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/3275#issuecomment-559257779

---

From: Ryan Hamilton <[email-masked]>
Date: Wed, 27 Nov 2019 13:34:34 -0800

@kazuho I'd be supportive of replacing DUPLICATE_PUSH with a duplicate PUSH_PROMISE which would, as @LPardue points out, revisit #1864. Otherwise I fear we end up with yet another sharp edge with push which seems highly sharp already.

-- 
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/3275#issuecomment-559258542

---

From: Martin Thomson <[email-masked]>
Date: Wed, 27 Nov 2019 14:47:11 -0800

I don't see any new information here.  The design we have is the result of balancing the need to have certain information in order with responses (as @RyanAtGoogle points out) and the desire to avoid the complexity and costs of duplication.

One advantage of the current design is the ability to exercise flexibility.  There is nothing preventing someone from pushing the same thing twice in this design, thereby avoiding DUPLICATE_PUSH.

Having implemented this, both at the server and the client, I can say that it is awkward, but not terribly so.  DUPLICATE_PUSH is a little tricky to thread on the server side, primarily due to it being hard to find something that is worth duplicating.  The client has no real problem with it other than the obvious ordering issue.  In that case, the simplest course is to proceed as though no push were promised until the PUSH_PROMISE arrives.

The possibility of priority inversion exists, but as pushes are discretionary, I don't see a significant downside.  Like QPACK, it's a discretionary feature and the server can make its own decisions about the risks.

-- 
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/3275#issuecomment-559276148

---

From: Ryan Hamilton <[email-masked]>
Date: Wed, 27 Nov 2019 15:01:50 -0800

I agree that there's no new information here; I just really dislike the sharp edge of the current DUPLICATE_PUSH behavior:

>  In that case, the simplest course is to proceed as though no push were promised until the PUSH_PROMISE arrives.

Indeed, that is the simplest course. But unless all user-agents do the same thing it means that servers can't rely on particular client-side behavior which is a real bummer. Push is already hard enough to get right (in both clients and servers) it seems like adding more rough edges runs the risk of resulting in push being disabled/removed from some implementations. (I believe Brad Lassey from Chrome presented at IETF 102 data which suggested doing exactly this)

-- 
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/3275#issuecomment-559279217

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Nov 2019 16:13:41 -0800

@LPardue @martinthomson Apologies for not chiming in to the discussion in #1864.

@martinthomson 
> One advantage of the current design is the ability to exercise flexibility. There is nothing preventing someone from pushing the same thing twice in this design, thereby avoiding DUPLICATE_PUSH.

I do not think that is something that a server should do, as it would mean that the server would be pushing not only the headers but the entire response multiple times to the client.

@RyanAtGoogle 
> But unless all user-agents do the same thing it means that servers can't rely on particular client-side behavior which is a real bummer. Push is already hard enough to get right (in both clients and servers) it seems like adding more rough edges runs the risk of resulting in push being disabled/removed from some implementations. (I believe Brad Lassey from Chrome presented at IETF 102 data which suggested doing exactly this)

+1

I can see @martinthomson's view that push is feature that the server might use, and therefore that it can be complicated. But as the number of traps increases in the protocol, it becomes more likely that some stacks (or some combination of client- and server-side stacks) would have edge cases that lead to worse performance when push is used.

In HTTP/2, push is a feature that is often misused, leading to performance degradation, even though the protocol itself is designed correctly. The situation would become worse in HTTP/3, if we introduce uncertain behaviors to the protocol design (i.e., some clients might not block on DUPLICATE_PUSH).

-- 
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/3275#issuecomment-559292834