Description
Issue
Currently, the number of streams opened by the client before it receives the initial SETTINGS
frame from the server is set to usize::MAX
by default.
Line 645 in 122091a
This is valid according HTTP/2 spec (in fact the spec doesn't mention any explicit limit here). However, opening unlimited number of streams can lead to majority of them being rejected by the server with REFUSED_STREAM
, if the server's SETTINGS_MAX_CONCURRENT_STREAMS
is set to any lower value. This is simply not ideal and waste of resources.
Proposal
To better handle this issue, I propose that we start with reasonably low number of streams by default, rather than the maximum possible number. The initial number is only used until the client receives the initial SETTINGS
frame, so it should be much more preferable to start with a low number in order to reduce the risk of REFUSED_STREAM
errors even if it might end up with lower utilization for a very short time.
What is the reasonable default number? The good number should be as low as what most servers would accept. In fact, the spec recommends that the value should be no smaller than 100. To back up this recommendation, I've done a quick research on the values of MAX_CONCURRENT_STREAMS
used by several popular servers. The result is as follows.
Server | MAX_CONCURRENT_STREAMS |
---|---|
google.com | 100 |
youtube.com | 100 |
x.com | 100 |
facebook.com | 100 |
www.amazon.com | 100 |
www.apple.com | 100 |
netflix.com | 100 |
microsoft.com | 100 |
cloudflare.com | 100 |
fastly.com | 100 |
hyper.rs | 100 |
openai.com | 128 |
www.rust-lang.org | 128 |
crates.io | 128 |
vercel.com | 250 |
tokio.rs | 256 |
deno.com | (unspecified) |
So if the client starts with 100 streams at maximum, all of these servers will accept them and the client will not receive any REFUSED_STREAM
errors. Therefore we can conclude that 100 is the reasonable default number of streams to start with.
Take a quick look at other HTTP/2 implementations
To further support the proposal, let's have a look at how other HTTP/2 client implementations are doing. In short, all of these three implementations adopt starting with the reasonably small number of streams.
nghttp2
nghttp2's default initial maximum number of concurrent streams is 100.
https://github.com/nghttp2/nghttp2/blob/837f0c67c7139aedfb0c31d252634c823bfa75ba/lib/includes/nghttp2/nghttp2.h#L2537-L2554
Go x/net/http2
Go also uses 100 as the the initial number of maximum concurrent strams.
https://cs.opensource.google/go/x/net/+/689bbc7005f6bbf9fac1a8333bf03436fa4b4b2a:http2/transport.go;l=56
One notable difference from nghttp2 is that Go will set 1,000 as the MAX_CONCURRENT_STREAMS
if the server doesn't provide one, while nghttp2 sets inifinity in this case.
Python httpx
httpx starts with just 1.
https://github.com/encode/httpcore/blob/2fcd062df71555cc7de55774c6dc137551eb8692/httpcore/_async/http2.py#L117-L119