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

http deadlock with stream FIN choices #1972

Closed
martinduke opened this issue Nov 6, 2018 · 22 comments · Fixed by #2003
Closed

http deadlock with stream FIN choices #1972

martinduke opened this issue Nov 6, 2018 · 22 comments · Fixed by #2003
Labels
-http design An issue that affects the design of the protocol; resolution requires consensus.

Comments

@martinduke
Copy link
Contributor

During interop, I couldn't interop with winquic because I never FIN my request stream, and winquic doesn't serve the response until the request FINs.

My conversation with @MikeBishop suggests that both of us are behaving legally, which is a problem. We should either force clients to FIN (allowing servers to wait for it) or not (not). I don't particularly care which.

@nibanks
Copy link
Member

nibanks commented Nov 6, 2018

This isn't really a spec issue, as this 0.9 HTTP design we have had for interop is not explicitly written anywhere. It has been a defacto requirement since the interop 2 or 3 to FIN your HTTP requests.

@martinduke
Copy link
Contributor Author

I'm referring to HTTP/3, although it came up in a 0.9 interop. I don't believe there is any requirement in the draft for clients to FIN their requests in a timely fashion.

@afrind
Copy link
Contributor

afrind commented Nov 6, 2018

HTTP/2 doesn't require a client FIN before the server will process the request, but you do need one to complete the stream state machine. I think HQ/H3 should be the same.

@kazuho
Copy link
Member

kazuho commented Nov 6, 2018

I agree with @nibanks.

HTTP/0.9 has two ways of terminating a request: CRLF and a FIN. HTTP/1 introduced the concept of request body along with a content-length request header field that allows a server to detect the end of the request without using FIN.

It is not the case with H2, where END_STREAM is the only way to terminate a request. I'd assume that that is the same with H3.

@RyanTheOptimist
Copy link
Contributor

RyanTheOptimist commented Nov 6, 2018 via email

@LPardue
Copy link
Member

LPardue commented Nov 6, 2018

Forcing client to FIN doesn't work because of things like CONNECT.

Section 3.1.2 of HTTP/QUIC says:

A CONNECT request in HTTP/QUIC functions in the same manner as in HTTP/2. The request MUST be formatted as described in [RFC7540], Section 8.3. A CONNECT request that does not conform to these restrictions is malformed. The request stream MUST NOT be half-closed at the end of the request.

@nibanks
Copy link
Member

nibanks commented Nov 6, 2018

Let's not take this discussion too far. All we are talking about is the simple HTTP 0.9 interop protocol we are currently using. We aren't talking HQ/H3. There is nothing but GET and a response body for the current interop. The (unofficial) requirements throughout interop have always been to FIN the request.

@LPardue
Copy link
Member

LPardue commented Nov 6, 2018

OK that makes sense. As you point out in the 2nd comment @nibanks this isn't really a spec issue, this was a subtlety I missed when skimming.

As someone that doesn't do interop, raising issues specific to it (and not the core documents) is awfully confusing. Especially so in future when search engine crawlers bring up old discussions as new implementers jump on board to build HTTP/QUIC software.

@mikkelfj
Copy link
Contributor

mikkelfj commented Nov 6, 2018

@nibanks Martin D. says it is concerning H3 in a comment in the above and he is OP.

@nibanks
Copy link
Member

nibanks commented Nov 6, 2018

Ok. Then I think we shouldn't be conflating the two issues of HTTP 0.9 interoperability issues and HQ/H3 design. I'm perfectly fine dedicating the rest of this issue to HQ/H3. For interop, the client FINs the 0.9 request.

@kazuho
Copy link
Member

kazuho commented Nov 6, 2018

Re HQ, I think we have a clear definition. Section 5.1 states that:

An HTTP request/response exchange fully consumes a bidirectional QUIC stream. After sending a request, a client closes the stream for sending; after sending a final response, the server closes the stream for sending and the QUIC stream is fully closed. Requests and responses are considered complete when the corresponding QUIC stream is closed in the appropriate direction.

I would interpret this statement that it is OK to use FIN as the way to determine the end of a HTTP request.

FWIW, this follows what we have had for H2 in RFC 7540 section 8.1.

Re CONNECT case that @LPardue points out, my understanding is that is considered either as an exception or that CONNECT elicits an early response.

@MikeBishop
Copy link
Contributor

@martinduke filed this issue after discussing with me; while he encountered it with HTTP/0.9 in the interop, the issue applies to HQ as well. What the draft currently says is:

An HTTP request/response exchange fully consumes a bidirectional QUIC stream. After sending a request, a client closes the stream for sending; after sending a final response, the server closes the stream for sending and the QUIC stream is fully closed. Requests and responses are considered complete when the corresponding QUIC stream is closed in the appropriate direction.

There's no normative language, but there's an expectation that the closure will happen at the end of the request. CONNECT is different, as noted, and (if you support CONNECT, at the very least), you have to look at the request to determine whether to expect it to be closed.

In the opposite direction, we say:

Changes to the state of a request stream, including receiving a QUIC RESET_STREAM with any error code, do not affect the state of the server’s response.

While this text is targeted at streams being reset or FIN'd before the request finishes, this could reasonably be interpreted/expanded to say that servers also cannot rely upon the FIN for finding the end of the request. I suspect that you need to process a complete request regardless of whether the stream has closed or not.

@martinduke
Copy link
Contributor Author

I apologize for giving the impression that I care about 0.9 at all with respect to the draft. As @MikeBishop says, the issue is filed about HTTP/3.

@MikeBishop Can you file a PR for whatever changes you think are needed?

@MikeBishop MikeBishop added design An issue that affects the design of the protocol; resolution requires consensus. -http labels Nov 13, 2018
@MikeBishop
Copy link
Contributor

MikeBishop commented Nov 14, 2018

@RyanatGoogle points out that Content-Length isn't mandatory, and when absent, end-of-stream is the only way to identify the end of the body (unless there are trailers). So perhaps we need to be having a more granular conversation here: You have to process headers (and discover whether you're expecting a body) without waiting for the body to arrive.

If no body is expected, process the request. If you expect a body and the length is given, read that many bytes. If you expect a body of unknown size, then and only then do you use end-of-stream as a signal.

@dtikhonov
Copy link
Member

Why shouldn't HTTP/3 behave the same way with regard to EOS as HTTP/2 and HTTP/1.x do?

@DavidSchinazi
Copy link
Contributor

@dtikhonov Because this is an opportunity to improve the status quo, like we did for TLS 1.3. Since implementors need to at least look at their EOS handling when transitioning from h2 to h3, now would be a good time to clarify the expected behavior.

@kazuho
Copy link
Member

kazuho commented Nov 14, 2018

While I agree that clarifying what endpoints would do is a good thing, I am not sure if we need to diverge from the model we have in HTTP/2.

In HTTP/2, end of a message was EOS, with the only exception being the CONNECT method. IIRC that was an intentional choice; that's one of the reasons RFC 8441 uses CONNECT instead of GET when opening a WebSocket channel.

At the same time, we have allowed the endpoints to start processing parts of the message as they arrive (e.g., early response, progressive rendering of responses).

I do not see a reason to change the model in HTTP/3, and therefore my preference goes to retaining the same model in order to reduce the differences between different HTTP versions as well as maximizing the chance to reuse extensions.

@DavidSchinazi
Copy link
Contributor

Ah, I got confused between http-core issue 22 and this one, apologies. I just had an opinion on clarifying that closing one half of a stream does not imply closing both sides. I agree that EOS should be the end of a message.

@RyanTheOptimist
Copy link
Contributor

I agree with @kazuho and @dtikhonov I don't see a good reason to diverge from HTTP/2's model here where end of stream ends the message (and "A request or response is also malformed if the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the body".)

@MikeBishop
Copy link
Contributor

In part because in HTTP/2 end-of-stream wasn't a separate thing -- it was a flag on the final header block or DATA frame. You couldn't end a stream elsewhere (though the workaround was a zero-length DATA frame with END_STREAM set).

In a more abstract sense, there are HTTP messages which aren't sent atomically -- they develop over time, and have to be handled in a streaming fashion. These might be streamed responses -- or requests, for a POST or a strained protocol adaptation. CONNECT fits into this category, since the "message body" and the "response body" are essentially infinite, and in HTTP/1.1 it was unique in being totally unframed. In HTTP/2, though, pseudo-chunked messages work exactly the same way.

To frame it differently, I don't have a problem with saying that EOS is the end of the message; I have a problem with the implication that you can wait for the entire message to arrive before you begin processing it. That's the original incompatible bug that started this issue.

@dtikhonov
Copy link
Member

[...] I have a problem with the implication that you can wait for the entire message to arrive before you begin processing it. That's the original incompatible bug that started this issue.

Is it? The first sentence in this thread reads:

During interop, I couldn't interop with winquic because I never FIN my request stream, and winquic doesn't serve the response until the request FINs.

Plus, it really depends on the type of the request? For example, if one is to calculate MD5 sum of the request body and return it as response body (something we did during last interop), one must wait for FIN. An echo-type service (another interop example) must process request body immediately (echo it back) and does not have to wait for FIN.

@RyanTheOptimist
Copy link
Contributor

RyanTheOptimist commented Nov 15, 2018 via email

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 a pull request may close this issue.

10 participants