]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: add HTTP2Config.StrictMaxConcurrentRequests
authorDamien Neil <dneil@google.com>
Wed, 25 Sep 2024 18:10:07 +0000 (11:10 -0700)
committerGopher Robot <gobot@golang.org>
Fri, 26 Sep 2025 23:14:14 +0000 (16:14 -0700)
Add a field to HTTP2Config controlling how we behave when an HTTP/2
connection reaches its concurrency limit.

This field will have no effect until golang.org/x/net/http2 is
updated to make use of it, and h2_bundle.go is updated with the
new http2 package.

For #67813

Change-Id: Ic72a0986528abb21649f28e9fe7cf6e1236b388d
Reviewed-on: https://go-review.googlesource.com/c/go/+/615875
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
api/next/67813.txt [new file with mode: 0644]
doc/next/6-stdlib/99-minor/net/http/67813.md [new file with mode: 0644]
src/net/http/http.go
src/net/http/transport_dial_test.go

diff --git a/api/next/67813.txt b/api/next/67813.txt
new file mode 100644 (file)
index 0000000..b420a14
--- /dev/null
@@ -0,0 +1 @@
+pkg net/http, type HTTP2Config struct, StrictMaxConcurrentRequests bool #67813
diff --git a/doc/next/6-stdlib/99-minor/net/http/67813.md b/doc/next/6-stdlib/99-minor/net/http/67813.md
new file mode 100644 (file)
index 0000000..74b8c76
--- /dev/null
@@ -0,0 +1,4 @@
+The new
+[HTTP2Config.StrictMaxConcurrentRequests](/pkg/net/http#HTTP2Config.StrictMaxConcurrentRequests)
+field controls whether a new connection should be opened
+if an existing HTTP/2 connection has exceeded its stream limit.
index 0f9165bf037ef30d40f66b4eb2cf6ab669b2496f..e7959fa3b6045b258f067f239ae1811255af0b3d 100644 (file)
@@ -235,10 +235,23 @@ type Pusher interface {
 // both [Transport] and [Server].
 type HTTP2Config struct {
        // MaxConcurrentStreams optionally specifies the number of
-       // concurrent streams that a peer may have open at a time.
+       // concurrent streams that a client may have open at a time.
        // If zero, MaxConcurrentStreams defaults to at least 100.
+       //
+       // This parameter only applies to Servers.
        MaxConcurrentStreams int
 
+       // StrictMaxConcurrentRequests controls whether an HTTP/2 server's
+       // concurrency limit should be respected across all connections
+       // to that server.
+       // If true, new requests sent when a connection's concurrency limit
+       // has been exceeded will block until an existing request completes.
+       // If false, an additional connection will be opened if all
+       // existing connections are at their limit.
+       //
+       // This parameter only applies to Transports.
+       StrictMaxConcurrentRequests bool
+
        // MaxDecoderHeaderTableSize optionally specifies an upper limit for the
        // size of the header compression table used for decoding headers sent
        // by the peer.
index d7f7a3d539434fa5faa150bd494ae44bc6b75abe..086039ece913d2d435c7a83856d57f11a924ad72 100644 (file)
@@ -59,7 +59,7 @@ func TestTransportPoolConnCannotReuseConnectionInUse(t *testing.T) {
 
 // When an HTTP/2 connection is at its stream limit
 // a new request is made on a new connection.
-func TestTransportPoolConnHTTP2OverStreamLimit(t *testing.T) {
+func testTransportPoolConnHTTP2NoStrictMaxConcurrentRequests(t *testing.T) {
        synctest.Test(t, func(t *testing.T) {
                dt := newTransportDialTester(t, http2Mode, func(srv *http.Server) {
                        srv.HTTP2 = &http.HTTP2Config{
@@ -100,6 +100,43 @@ func TestTransportPoolConnHTTP2OverStreamLimit(t *testing.T) {
        })
 }
 
+// When an HTTP/2 connection is at its stream limit
+// and StrictMaxConcurrentRequests = true,
+// a new request waits for a slot on the existing connection.
+func TestTransportPoolConnHTTP2StrictMaxConcurrentRequests(t *testing.T) {
+       t.Skip("skipped until h2_bundle.go includes support for StrictMaxConcurrentRequests")
+
+       synctest.Test(t, func(t *testing.T) {
+               dt := newTransportDialTester(t, http2Mode, func(srv *http.Server) {
+                       srv.HTTP2.MaxConcurrentStreams = 2
+               }, func(tr *http.Transport) {
+                       tr.HTTP2 = &http.HTTP2Config{
+                               StrictMaxConcurrentRequests: true,
+                       }
+               })
+
+               // First request dials an HTTP/2 connection.
+               rt1 := dt.roundTrip()
+               c1 := dt.wantDial()
+               c1.finish(nil)
+               rt1.wantDone(c1, "HTTP/2.0")
+
+               // Second request uses the existing connection.
+               rt2 := dt.roundTrip()
+               rt2.wantDone(c1, "HTTP/2.0")
+
+               // Third request blocks waiting for a slot on the existing connection.
+               rt3 := dt.roundTrip()
+
+               // First request finishing unblocks the thirrd.
+               rt1.finish()
+               rt3.wantDone(c1, "HTTP/2.0")
+
+               rt2.finish()
+               rt3.finish()
+       })
+}
+
 // A new request made while an HTTP/2 dial is in progress will start a second dial.
 func TestTransportPoolConnHTTP2Startup(t *testing.T) {
        synctest.Test(t, func(t *testing.T) {