Skip to content
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

Push ID #701

Merged
merged 5 commits into from Aug 4, 2017
Merged

Push ID #701

merged 5 commits into from Aug 4, 2017

Conversation

martinthomson
Copy link
Member

@martinthomson martinthomson commented Jul 27, 2017

As requested, I split this out of the unidirectional PR (#643).

There is a change here, that needs to be highlighted. @kazuho points out that without inter-stream ordering guarantees, requests might be triggered on multiple streams. The server then has an awkward choice: either push multiple times, or risk the client triggering requests when streams arrive out of order. This contains a fix for that bug.

The fix is to allow the same push ID to be used in multiple PUSH_PROMISE frames. Then, fulfillment happens at most once.

I think that this is a neat fix, but it creates two more problems:

  1. How does a client know when the server will stop referencing a push ID in PUSH_PROMISE? With reordering, the pushed response might arrive and be consumed before a PUSH_PROMISE that references it. I think that it might be possible to add a counter to the push stream header that says how many times the server promised this response. The problem with that is that if a stream that carries the PUSH_PROMISE is reset, then that counter will be too high. I've decided to just explain the issue and add some weasel wording rather than provide a direct fix.

  2. What if the server provides different headers for the same Push ID? I've chosen to fix this by simply forbidding variations in the content of the header block. I considered only allowing the headers to appear once, but then you have to deal with that stream being reset. Repetition should be rare enough, and header compression might be able to help with the extra bytes.

I think that those problems are both less serious than the bug this fixes. Besides, the first is really only an generalized form of a problem we have already: the stream carrying a PUSH_PROMISE can be reset. What currently happens is that the server more or less has to send a push stream anyway, even though it can't know whether the client got the PUSH_PROMISE. With this, a server can use CANCEL_REQUEST.

Closes #702, #281.

@martinthomson martinthomson added -http design An issue that affects the design of the protocol; resolution requires consensus. labels Jul 27, 2017
@MikeBishop
Copy link
Contributor

I'll note that, with unidirectional streams, we could use a stream from the server for the request headers as well, and then only have them once. The problem with that is that, until they're processed, the client doesn't know what request it is that it shouldn't be making as it processes the original resource.

prior to the response stream being created. This frame can be used to cancel
server push requests that are initiated with a PUSH_PROMISE.

The frame identifies a server push request is identified by Push ID (see
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in here somewhere.. "request that is identified by" perhaps?

@martinthomson
Copy link
Member Author

@MikeBishop, I considered that. Getting the ordering guarantees right here is tricky and I think that it's unfair on clients to say "I'm going to promise you something", but not tell them exactly what.

Copy link
Contributor

@MikeBishop MikeBishop left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my feedback is editorial -- the core of the change looks good.

frames, and response body (if any) carried by DATA frames.
After the push stream header, a push contains a response ({{request-response}}),
with response headers and (if present) trailers carried by HEADERS frames sent
on the control stream, and response body (if any) carried by DATA frames.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on the control stream.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, you noticed that, but then you didn't notice that the ordering was completely wrong :)


PUSH_DEPENDENT (0x02):
: Indicates a dependency on a pushed request. If set, this flag indicates
that the Dependent Stream identifies a response stream and that the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saying that it "identifies a response stream" in both these places feels a little odd. If it's the stream ID, then just say it's the stream ID and you don't even need the flags. If it's the Push ID, use the term here for clarity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I can remove the second sentence and rely on the description of the fields below.

updated.
Prioritized Request:
: A 32-bit identifier for a request. This contains the stream ID of a request
stream when the PUSH_PRIORITIZED flag is clear, or a Push ID when the PUSH
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PUSH => PUSH_PRIORITIZED?

Setting the PUSH or PUSH_DEPENDENT flag causes the frame to identify a
PUSH_PROMISE using a Push ID (see {{frame-push-promise}} for details).

A PRIORITY frame MAY identify no request by using a stream ID of 0; as in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are two references in a PRIORITY frame, and setting one of them to zero is prohibited by your other pull request, perhaps be more specific than "identify" here.

### CANCEL_REQUEST {#frame-cancel-request}

The CANCEL_REQUEST frame (type=0x3) is used to request cancellation of a request
prior to the response stream being created. This frame can be used to cancel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that there aren't (currently 😉) separate request/response streams, this basically can only be used to cancel a push. Let's call it CANCEL_PUSH for now and not talk about requests in this section.


A server can send this frame to indicate that it won't be sending a response
prior to creation of a push stream. Once the push stream has been created, a
RST_STREAM with a CANCELLED code can be used instead.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"can" feels like it needs an RFC2119 WORD, but I don't know which.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone with SHOULD. There isn't any point in MUST here, because it's not a correctness issue, it's just pointless to use CANCEL_PUSH at this point.

RST_STREAM with a CANCELLED code can be used instead.

A CANCEL_REQUEST frame is sent on the control stream. Sending a CANCEL_REQUEST
frame on a stream other than the control stream MUST be treated as a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"stream error of type X" avoids the argument of whether to do a/an before HTTP.

| Push ID (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~~~~~~~~~~
{: #fig-cancel-request title="CANCEL_REQUEST frame payload"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been trying to avoid drawings of things that have only a single field and call that single thing the payload of the frame.

: HPACK-compressed request headers for the promised response.
Push ID:
: A 32-bit identifier for the server push request. This identifier is used in
push stream header ({{server-push}}), CANCEL_REQUEST frames
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"in the" or "in ... headers". "In ... header" sounds odd.

@MikeBishop
Copy link
Contributor

Oh, and fill in the changelog.

stream ID of a request stream when the PUSH_DEPENDENT flag is clear, or a
Push ID when the PUSH_DEPENDENT flag is set. A request stream ID of 0
indicates a dependency on the root stream. For details of dependencies,
see {{priority}} and {!RFC7540}}, Section 5.3.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unbalanced braces


A PRIORITY frame MAY identify no request in the Prioritized Request field by
using a stream ID of 0; as in {{!RFC7540}}, this makes the request dependent on
the root of the dependency tree.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Prioritized Request" is the request whose priority is being modified. Per the following paragraph, this cannot be zero (or one). I think you can delete this paragraph / merge it with the following one.

### CANCEL_PUSH {#frame-cancel-push}

The CANCEL_PUSH frame (type=0x3) is used to request cancellation of server push
prior to the push stream being created. The CANCEL_REQUEST frame identifies a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CANCEL_REQUEST => CANCEL_PUSH

Copy link
Contributor

@ianswett ianswett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit.

({{frame-push-promise}}). Other than the means of identifying requests, the
prioritization system is identical to that in HTTP/2.

Only a client can prioritize requests. A server MUST NOT send a PRIORITY frame.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to be pedantic, but a server can definitely prioritize requests, even if it's not via the PRIORITY frame. How about saying "Only the client can SEND a PRIORITY frame."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. I think it's more precise to say PRIORITY frame.
FWIW, I think it's a good idea that we're adding this requirement to avoid ambiguity and further down below, to avoid having to deal with reordering between PUSH_PROMISE and PRIORITY frames from server to client.

Copy link
Contributor

@janaiyengar janaiyengar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of editorial comments, and one material one.

  1. I think we need to be able to handle potential reordering between PUSH_PROMISE and CANCEL_PUSH.
  2. I don't think it makes sesne to say "push response" and "server push request", so I propose "promised resource".
  3. There's awkward phrasing around Prioritized Request and Dependent Request in text surrounding the PRIORITY frame. I propose adding "ID" to these field names to make the text less awkward.

({{frame-push-promise}}). Other than the means of identifying requests, the
prioritization system is identical to that in HTTP/2.

Only a client can prioritize requests. A server MUST NOT send a PRIORITY frame.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. I think it's more precise to say PRIORITY frame.
FWIW, I think it's a good idea that we're adding this requirement to avoid ambiguity and further down below, to avoid having to deal with reordering between PUSH_PROMISE and PRIORITY frames from server to client.

and is substantially different from {{!RFC7540}}. In order to support ordering,
it MUST be sent only on the control stream. The format has been modified to
accommodate not being sent on-stream and the larger stream ID space of QUIC.
and is substantially different in format from {{!RFC7540}}. In order to ensure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove "substantially"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an echo of language elsewhere in the HTTP draft, indicating which frames are mostly-direct copies of the HTTP/2 equivalent, and which ones are fundamentally different. Happy to discuss rephrasing that all-up in a separate editorial issue, but I think following the existing pattern in this PR is entirely appropriate.

request is dependent. A request is identified by the stream ID of a request
when the corresponding PUSH_PRIORITIZED or PUSH_DEPENDENT flag is not set.
Setting the PUSH or PUSH_DEPENDENT flag causes the frame to identify a
PUSH_PROMISE using a Push ID (see {{frame-push-promise}} for details).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it identifies the PUSH_PROMISE, but the promised resource, right?

A PRIORITY frame MAY identify a dependent stream with a stream ID of 0; as in
A PRIORITY frame identifies a request to priotize, and a request upon which that
request is dependent. A request is identified by the stream ID of a request
when the corresponding PUSH_PRIORITIZED or PUSH_DEPENDENT flag is not set.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this sentence. Can you rephrase? Specifically "A request is identified by the stream ID of a request" is really awkward. Perhaps saying "prioritized request" and "dependent request" might help? I believe what you're trying to say is: "A prioritized request is identified by its stream ID when the corresponding PRIORITY frame does not have PUSH_PRIORITIZED or PUSH_DEPENDENT flags set. Setting these flags causes the PRIORITY frame to identify a pushed resource using a Push ID (see {{frame-push-promise}} for details)." WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reworded a little. I've used the field names more explicitly. I didn't want to use "prioritized request" because that is too close to "Prioritized Request ID".

Prioritized Request:
: A 32-bit identifier for a request. This contains the stream ID of a request
stream when the PUSH_PRIORITIZED flag is clear, or a Push ID when the
PUSH_PRIORITIZED flag is set.

Stream Dependency:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Dependent Request. "Stream Dependency" is not a field in the frame.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think that "dependent request" means the stream that is dependent, so I changed the name. I'll fix the picture.

: HPACK-compressed request headers for the promised response.
A server MAY use the same Push ID in multiple PUSH_PROMISE frames. This allows
the server to use the same server push in response to multiple concurrent
requests. Referencing the same server push ensures that a PUSH_PROMISE can be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Referencing the same promised resource"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, there's nothing stopping you from having two different pushes for the same resource in flight at once. I'm drawing a blank on why you would do that, but I'm not sure we want to foreclose it here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's another reason to talk about "requests" rather than resources.

the server to use the same server push in response to multiple concurrent
requests. Referencing the same server push ensures that a PUSH_PROMISE can be
made in relation to every response in which server push might be needed without
causing duplicating pushes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"causing duplicating pushes" --> "duplicating pushes"


A server SHOULD avoid sending a PUSH_PROMISE that includes a Push ID that was
fulfilled prior to the request being made. Though a client needs to handle
receiving a promised response prior to it being promised due to reordering of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested rewording: "Due to the possibility of network reordering, a client must be able to receive a promised resource prior to the corresponding PUSH_PROMISE."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a completely different statement.

fulfilled prior to the request being made. Though a client needs to handle
receiving a promised response prior to it being promised due to reordering of
stream delivery, clients might be unable to reuse a pushed response that was
long since consumed. \[Editor's Note: should we include a count of the number of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this phrase means "clients might be unable to reuse a pushed response that was long since consumed." Can you clarify?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A server might promise with Push ID X and fulfill that promise. Later, it could send the same PUSH_PROMISE for X and assume that the client already has the response. A client can't be expected to retain the response. I have a different proposal for handling this now that I've had time away from the problem, I'll write that down.


SETTINGS (0x4):
: SETTINGS frames are sent only at the beginning of the connection. See
{{frame-settings}} and {{h2-settings}}.

PUSH_PROMISE (0x5):
: See {{frame-push-promise}}.
: The PUSH_PROMISE does not reference the stream that carries the response to
the pushed request; instead the push stream references the PUSH_PROMISE frame.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reword. suggestion: "The PUSH_PROMISE does not reference the stream carrying the promised resource; instead the push stream references the PUSH_PROMISE frame."

A PRIORITY frame MAY identify a Stream Dependency with a stream ID of 0; as in
{{!RFC7540}}, this makes the request dependent on the root of the dependency
request is dependent. A Prioritized Request ID or Stream Dependency ID
identifies a client initiated request using the corresponding stream ID when the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe client-initiated should be hyphenated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@MikeBishop
Copy link
Contributor

Random question while reading -- is '0' a valid Push ID? The answer isn't entirely obvious, and I could see implementations differing painfully.

@martinthomson
Copy link
Member Author

I hope to fix the 0 thing in a follow-on. See my response to Jana on the mailing list. Right now, the entire 32-bit space is open for business.

@MikeBishop MikeBishop merged commit 2a65451 into master Aug 4, 2017
@MikeBishop MikeBishop deleted the push-id branch August 4, 2017 20:45
Copy link
Contributor

@janaiyengar janaiyengar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Looking forward to the follow on.

A PRIORITY frame MAY identify a Stream Dependency with a stream ID of 0; as in
{{!RFC7540}}, this makes the request dependent on the root of the dependency
request is dependent. A Prioritized Request ID or Stream Dependency ID
identifies a client initiated request using the corresponding stream ID when the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
-http design An issue that affects the design of the protocol; resolution requires consensus.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Concurrent server pushes for the same resource
5 participants