Subject: [quicwg/base-drafts] What should the receiver do when the peer closes a unidirectional stream before providing the stream type? (#2331)

From: Kazuho Oku <[email-masked]>
Date: Fri, 11 Jan 2019 21:55:12 -0800

The H3 draft seems to be silent about what should be done, and we have two options:
* close the connection with a `HTTP_CLOSED_CRITICAL_STREAM` error, because it might have been a control stream or QPACK stream
* ignore that, because H3 endpoints are permitted to start using new unidirectional streams without negotiation

I prefer the former, because sending the stream type is a requirement for any unidirectional stream. However, the name of the error code is counterintuitive for an error code to be sent in response to a closure of a stream that might not have been critical.

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

---

From: MikkelFJ <[email-masked]>
Date: Sat, 12 Jan 2019 00:47:43 -0800

The question is if you can close a stream after sending a frame type, then loose a packet so the receiver is not aware of the type at the time it responds to the close.

If this is not the case, notably when the stream is closed with final size 0, I don't think it should be a considered a critical stream but rather an optional stream that never came to happen. After all, the sender (ought to) know what it is doing.

-- 
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/2331#issuecomment-453731137

---

From: Kazuho Oku <[email-masked]>
Date: Sat, 12 Jan 2019 01:14:20 -0800

@mikkelfj I do not think that we have an API requirement that the two cases that you describe need be distinguishable.

IMO, a QUIC stack should not be required to communicate the final size when it receives a RST_STREAM frame. An H2 implementation using such a QUIC stack will not be able to distinguish between when it receives RST_STREAM (final_size=1) before seeing any STREAM frame, and when it receives RST_STREAM (final_size=0).

Therefore, IMO the issue is what an H3 endpoint should do when it receives a RST_STREAM (and also a FIN, depending on the outcome of the discussion) before receiving the first byte.

-- 
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/2331#issuecomment-453732566

---

From: MikkelFJ <[email-masked]>
Date: Sat, 12 Jan 2019 01:59:47 -0800

(It's now RESET_STREAM)

You can close normally (FIN), or with RESET_STREAM. In both cases there is a final size. The difference is that RESET_STREAM does not require unsent or lost data to be (re)transmitted. (I'm sure you are aware of all this).

If you get a RESET_STREAM and you do not have the stream type, there is no point in waiting for it. If you get a FIN without a type and a final size >= 1 but have not received the first byte, you can choose to wait for it. You probably should - but an implementation could make a mistake here assuming an error if an internal struct says type = type_missing. I take it you issue is intended for RESET only, but you were not clear on that.

Now, further analysis:
Assume you send a non critical stream with a type, and some other data. Then decide to RESET_STREAM in a later packet. This later packet overtakes the previous packet holding the type. Since the receiver cannot assume that the type was ever sent or will ever be received, it must act on what it knows. Hence, if it treats this as a critical error, streams can never be reset. Consequently, either streams should never be reset, or a reset without a type should not be treated as critical. I.e. a stream only exists for real once a type is positively known.

-- 
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/2331#issuecomment-453734970

---

From: MikkelFJ <[email-masked]>
Date: Sat, 12 Jan 2019 02:01:50 -0800

(I think you edited your previous text while I got distracted editing my response, at least you distinguish between FIN and RESET contrary to what I suggest in the above. Regardless, I stand by what I wrote).

-- 
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/2331#issuecomment-453735071

---

From: Lucas Pardue <[email-masked]>
Date: Sat, 12 Jan 2019 02:42:54 -0800

Nice spot @kazuho, I prefer your second option.

I do wonder how likely this is to happen in practice. An endpoint opens the stream with a STREAM frame, and it would probably be wise for them to include the type byte in that initial frame.

If I understand what Mikkel said, the endpoint sends and empty stream frame and then closes the stream. What is advantage in that style of opening?

-- 
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/2331#issuecomment-453737469

---

From: MikkelFJ <[email-masked]>
Date: Sat, 12 Jan 2019 04:33:45 -0800

> If I understand what Mikkel said, the endpoint sends and empty stream frame and then closes the stream. What is advantage in that style of opening?

First, it is a matter of being robust against all possible ways of transmitting packets, even if done in an unlikely way.

Second, in my last analysis I have a case where you send valid data, but the later RESET races the earlier packets. There are several cases: the open is entirely missed and only the RESET survives, but it then effectively opens and closes the stream, or the open is sent with 0 length data, then a later packet sends the frame type, and a later packet again sends the RESET, and the middle packet gets lost or delayed. The latter case is of course contrived, but it is still valid and possible.

-- 
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/2331#issuecomment-453743962

---

From: Lucas Pardue <[email-masked]>
Date: Sat, 12 Jan 2019 04:57:15 -0800

I understand we need to be robust and that's why I like the Kazuho's second option. It's just going to be a bit disappointing if H3 implementations fail at creating the 4 unidirectional streams that are fundamental to the application session. 

-- 
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/2331#issuecomment-453745447

---

From: MikkelFJ <[email-masked]>
Date: Sat, 12 Jan 2019 05:17:32 -0800

Well, if the peer intentionally isn't creating the necessary streams, the connection won't work. It's not like you accidentally send a reset just because. There are endless other ways to do that, like never sending a response to a request.

-- 
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/2331#issuecomment-453746753

---

From: Kazuho Oku <[email-masked]>
Date: Sat, 12 Jan 2019 14:22:09 -0800

@mikkelfj Yes, I did edit my comment in order to clarify what I meant, while you tried to confirm what we have now. Thank you for the clarification and sorry for the confusion.

> Hence, if it treats this as a critical error, streams can never be reset. Consequently, either streams should never be reset, or a reset without a type should not be treated as critical. I.e. a stream only exists for real once a type is positively known.

Yes. To be precise, calling the symptom a critical error would mean that the sender cannot reset a stream until it knows that the peer has received the first octet. But it is often hard for the application to "know" that the peer has received the first octet. For example, the sender will in practice never know that the first octet of the control stream has been received by the peer if the values sent in the SETTINGS frame were: NUM_PLACEHOLDERS=0, QPACK_MAX_TABLE_CAPACITY=0.

OTOH, the downside of _not_ calling that a critical error means that the connection state might not advance at all, if the server creates a control stream and then resets it before the first byte is delivered to the client. In such case, a client that waits for SETTINGS frame before sending any request will stall forever. However, we might argue that such stall would also happen when the packet that carries the control stream gets always dropped on the network (for some reason), and that a sender resetting the control stream is as unlikely as that.

In the end, now I think I might prefer the second option (i.e. ignore).

-- 
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/2331#issuecomment-453785366

---

From: Lucas Pardue <[email-masked]>
Date: Sat, 12 Jan 2019 14:27:23 -0800

Given that resetting a control stream is going to cause a connection error, I don't know why an implementation would ever try to do that. The risk is high, what is the benefit?

-- 
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/2331#issuecomment-453785642

---

From: Kazuho Oku <[email-masked]>
Date: Sat, 12 Jan 2019 15:40:06 -0800

@LPardue I agree that nobody would try to do that in practice (although in a very rare case that might happen due to some other reason; e.g. low memory). Anyways, there are million ways to block the protocol from proceeding, and I do not think it's necessary to detect this particular condition.

In contrast, allowing the sender of an extended unidirectional stream to reset the stream might have practical benefit. For example, a sender can speculatively create a unidirectional stream then reset it when the received SETTINGS indicate that the peer does not support the necessary feature. Using reset makes sense especially when the sender has sent certain amount of bytes on the stream; avoiding retransmission (if possible) is beneficial.

That's why I now think the second option (ignore early close) is preferable.

-- 
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/2331#issuecomment-453789586

---

From: Subodh Iyengar <[email-masked]>
Date: Sat, 12 Jan 2019 15:46:12 -0800

We encountered this as well, however we implicitly chose to go with the second option. I think it is the right option because the stream can be opened as a result or not just receiving the a type byte but also by a BLOCKED frame type or some other frames associated with a stream. For example a receiver of a unidirectional stream could choose to have an initial flow window of 0 bytes (weird but possible).

-- 
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/2331#issuecomment-453789857

---

From: Kazuho Oku <[email-masked]>
Date: Sat, 12 Jan 2019 17:24:06 -0800

Thanks all. Filed #2332.

-- 
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/2331#issuecomment-453794131

---

From: Mike Bishop <[email-masked]>
Date: Wed, 16 Jan 2019 08:30:56 -0800

Closed #2331 via #2332.

-- 
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/2331#event-2077905646