Skip to content

Proposal: change the default value of initial_max_send_streams to 100 #731

Closed
Cysharp/YetAnotherHttpHandler
#71
@magurotuna

Description

@magurotuna

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.

initial_max_send_streams: usize::MAX,

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions