Change:
This goes someway to fixing and . Setting low values for unidirectional stream transport parameters means that HTTP/3 connections get stuck early. Although client and server can dance around, sending BLOCKED frames and increasing limits, we should clearly highlight the problem and suggest some sensible, bare minimum, values.
I'm happy to take it out. However, I wouldn't call it misbehaviour to exercise protocol features. The unidirectional stream extension capabilities seem dead on arrival if there is no way for either peer to express their intent.
If the transport parameter is not set, HTTP 3 will just stall. That's because the HTTP3 implementations are supposed to send the "Settings" before sending any request or response, but they can only do that if there are flow control credits for Uni streams. Of course, the HTTP3 spec says that "Both clients and servers SHOULD send a value of three or greater for the QUIC transport parameter initialmaxunistreams." But it fails to mention that there should be some sensible value for . The bug was found during interop testing between and . It really is a spec bug. Section 3.1 of the HTTP3 spec has text stating that "it is recommended that be no smaller than 100, so as to not unnecessarily limit parallelism." I think that we need similar text in section 3.2.
I agree that we should probably recommend sending a non-zero value, but the connection is not supposed to stall in either case. If a peer sends 0 in the transport parameters, it will have to grant flow control credit via MAX_DATA frames.
NAME the problem is that if the number of credit is zero, the peer cannot even open the stream. It is not supposed to send a null length stream frame. And if the stream is not even open, the generic transport has no way to know that it should send a MAXSTREAMDATA frame on stream 2 or 3. An H3 conscious transport might know that, but a generic one not so much -- you don't need credits on stream 2 or 3 for HTTP 1.x or for DNS over HTTP. It seems much preferable to just mandate non zero values in the transport parameters.
NAME Why can't you open the stream? There's nothing preventing you from opening a stream that you don't have flow control credit for. You could send a zero-length STREAM frame, or even better, you could send a STREAM_BLOCKED frame.
NAME I suppose you could send a zero length STREAM frame, although I am pretty sure one of spec says that nodes should not send zero-length stream frames because DOS attacks. STREAMBLOCKED has a different meaning -- it does not actually open the stream. I suppose that STREAMDATA_BLOCKED would work. In any case, asking for a sensible initial limit seems much simpler.
STREAMDATABLOCKED is what I meant, sorry. Too many BLOCKED frames. STREAMBLOCKED doesn't even exist... I think this is a general issue with either picoquic or ats, and not H3 specific. No matter what the application protocol, a peer can send you 0-values for stream counts and for flow control limits, and the protocol should still work (although slower, since the MAX frames need to be exchanged). There should never be a deadlock (unless the peer is refusing to grant credits, even at a later point in time).
It is not a "dead" lock. If picoquic does not have credits, it will just wait. If the peer somehow wakes up and provide credit, transmission will resume. In my mind, the transmission of the various "BLOCKED" frames is just optional.
This is slightly related to and . IMO there is no easy fix because even if you grant some initial values, there is nothing forcing the endpoint to use the first available unidirectional streams for control streams. The temptation is to set these transport limits quite high in order to just avoid the issue but that allows the sending endpoint to overwhelming the receiver with a lot of data (negating the value of the parameters). An alternative is to set a conservative value for , say 8 bytes. This would allow the sender to open a uni stream with a type, the receiver can use this to determine if/how to grant further credits.
In Tokyo 2019, we discussed (Avoid creating QPACK streams when unnecessary) and came upon the conclusion: Until such a change is applied to the HTTP and QPACK specs, we rely on the guidance in section 3.2: the QUIC transport parameter "initialmaxunistreams". There is an implicit assumption in this present state that assumes these three streams will be used for the "critical streams" - Control stream, QPACK Encoder Stream and QPACK Decoder stream. The outcome of the Tokyo discussion didn't seem to me to identify the impact on initialmaxunistreams. HTTP/3 reserves code points for GREASING but doesn't provide much guidance on their usage. It says "MUST ignore" and CAN send reserved stream types. In contrast Transport is a little stronger and says that: Not sending GREASE negates its powers. GREASE streams are not the only consumer of unidirectional credit. It seems feasible that an endpoint might optimise for some use cases and decide push on 2-out-of-three streams it was given the credit for. This depends on receiving MAXPUSHID on the control stream, of course. The control stream unfortunately has dubious language: the connection. When is the beginning of beginning and when is the end of beginning? Mike somewhat answered this question in (Default settings in HTTP): Is the end the beginning once SETTINGS have been sent? PR was an attempt at scurry along the spec change wanted. Created in response to hitting an interop issue causes by the old requirement to "MUST create one QPACK Encoder and Decoder stream" conflicting with the "SHOULD allow initial max 3". However, the issue I see with the way the specs would look like with this specification is that there is not much determinism on how an endpoint should use MAXSTREAMS and how it can the unidirectional streams it makes available will be used. For example, a client advertises 3 initial max uni streams, likewise a server advertises 3 a server creates a 2 grease streams and 1 encoder stream Recall the Tokyo outcome was "creating them is not forbidden even if the setting prevents their use" and by default value of SETTINGSQPACKMAXTABLECAPACITY is 0 if you never receive a SETTING that tells you otherwise the client expects 3 "critical streams", so banks the difference (2) and gives unidirectional stream credit by sending a MAXSTREAMS frame of value 5 the client opens a grease stream, a control stream, and 1 encoder stream the client sends SETTINGSQPACKMAXTABLECAPACITY = Y the client sends MAXPUSHID = 3 the client sends a request with static compression the server sends 3 PUSHPROMISES and attempts to push 3 resources. the server wishes to open N unidirectional extension streams the server sends STREAMSBLOCKED with the value of 5 the client sends a request with dynamic compression the server wishes to open a decoder and sends STREAMSBLOCKED with the value of 5 a) What value of MAXSTREAMS is suitable for a client to send? b) if the value is < N+2 what is a suitable rate to send STREAMSBLOCKED? c) will this just devolve to a cycle of one endpoint sending STREAMSBLOCKED and the other echoing back MAX_STREAMS (value +1) until the other side shuts up?
It seems to me that this is the worst case, and that if we think it's fine then every other questions can be considered fine. My position is that having this worst case is fine. Many types of greasing (e.g., version negotiation, retry, HelloRetryRequest) introduces one extra roundtrip. So having one extra round-trip when opening one greasing stream sounds not unreasonable.
I'm ok with that but it also risks affecting all unidirectional stream types. Server push somewhat skirts the issue by providing a signal (counting PUSHPROMISEs) that enhances the clients knowledge. QPACK is part way there because non-zero SETTINGSQPACKMAXTABLECAPACITY implies that some credit from both ends is required. Perhaps we could summarise some guidance for people about the RTT incurred by these streams and ways to avoid them. A totally different alternative that might turn out in practice easier is to simply set maxinitialunistreams to a very large number, and set the initialmaxstreamdatauni to a low number (8 bytes would be enough to peak at the stream type). This way the client has far more insight into what the other side is attempting to do and can exercise stream control as suited to its needs. All while avoiding this weird cycle.
Agreed. Recommending "at least three, but more is better because there could be other types of streams" makes sense to me.
I think that this has resolved into an editorial-level conclusion. (That I agree with.)
I'll create a PR
Realistically, I think an implementation ought to have a higher number than three here, but the actual requirement for interop will be three much of the time, so I think this is good guidance. Note that STREAM_BLOCKED doesn't get sent over and over -- if the peer decides not to give you more credit and that means you can't use your extensions, tough luck.A minor nit, but LGTM otherwise.

Discussion:
streams (push-streams).  Other stream types can be defined by extensions to HTTP/3; see extensions for more details. <del> Both clients and servers SHOULD send a value of three or greater for the QUIC transport parameter "initial_max_uni_streams". </del> <ins> The performance of HTTP/3 connections in the early phase of their lifetime is sensitive to the creation and exchange of data on unidirectional streams.  Endpoints that set low values for the QUIC transport parameters "initial_max_uni_streams" and "initial_max_stream_data_uni" will increase the chance that the remote peer reaches the limit early and becomes blocked.  In particular, the value chosen for "initial_max_uni_streams" should consider that remote peers may wish to exercise reserved stream behaviour (stream-grease).  To reduce the likelihood of blocking, both clients and servers SHOULD send a value of three or greater for the QUIC transport parameter "initial_max_uni_streams", and a value of 1,024 or greater for the QUIC transport parameter "initial_max_stream_data_uni". </ins> If the stream header indicates a stream type which is not supported by the recipient, the remainder of the stream cannot be consumed as