Skip to content

Commit fe9bcbc

Browse files
committed
http2: support HTTP2Config.StrictMaxConcurrentRequests
When HTTP2Config.StrictMaxConcurrentRequests is set (added in Go 1.26), use it to override the value of Transport.StrictMaxConcurrentStreams. Permits configuring this parameter from net/http without importing x/net/http2. For golang/go#67813 Change-Id: Ie7fa5a8ac033b1827cf7fef4e23b5110a05dc95f Reviewed-on: https://go-review.googlesource.com/c/net/+/707315 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Nicholas Husin <[email protected]> Reviewed-by: Nicholas Husin <[email protected]>
1 parent c492e3c commit fe9bcbc

File tree

5 files changed

+64
-13
lines changed

5 files changed

+64
-13
lines changed

http2/config.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
// - If the resulting value is zero or out of range, use a default.
2828
type http2Config struct {
2929
MaxConcurrentStreams uint32
30+
StrictMaxConcurrentRequests bool
3031
MaxDecoderHeaderTableSize uint32
3132
MaxEncoderHeaderTableSize uint32
3233
MaxReadFrameSize uint32
@@ -64,12 +65,13 @@ func configFromServer(h1 *http.Server, h2 *Server) http2Config {
6465
// (the net/http Transport).
6566
func configFromTransport(h2 *Transport) http2Config {
6667
conf := http2Config{
67-
MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
68-
MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
69-
MaxReadFrameSize: h2.MaxReadFrameSize,
70-
SendPingTimeout: h2.ReadIdleTimeout,
71-
PingTimeout: h2.PingTimeout,
72-
WriteByteTimeout: h2.WriteByteTimeout,
68+
StrictMaxConcurrentRequests: h2.StrictMaxConcurrentStreams,
69+
MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
70+
MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
71+
MaxReadFrameSize: h2.MaxReadFrameSize,
72+
SendPingTimeout: h2.ReadIdleTimeout,
73+
PingTimeout: h2.PingTimeout,
74+
WriteByteTimeout: h2.WriteByteTimeout,
7375
}
7476

7577
// Unlike most config fields, where out-of-range values revert to the default,
@@ -128,6 +130,9 @@ func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) {
128130
if h2.MaxConcurrentStreams != 0 {
129131
conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
130132
}
133+
if http2ConfigStrictMaxConcurrentRequests(h2) {
134+
conf.StrictMaxConcurrentRequests = true
135+
}
131136
if h2.MaxEncoderHeaderTableSize != 0 {
132137
conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize)
133138
}

http2/config_go125.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !go1.26
6+
7+
package http2
8+
9+
import (
10+
"net/http"
11+
)
12+
13+
func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
14+
return false
15+
}

http2/config_go126.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.26
6+
7+
package http2
8+
9+
import (
10+
"net/http"
11+
)
12+
13+
func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
14+
return h2.StrictMaxConcurrentRequests
15+
}

http2/transport.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ type ClientConn struct {
355355
readIdleTimeout time.Duration
356356
pingTimeout time.Duration
357357
extendedConnectAllowed bool
358+
strictMaxConcurrentStreams bool
358359

359360
// rstStreamPingsBlocked works around an unfortunate gRPC behavior.
360361
// gRPC strictly limits the number of PING frames that it will receive.
@@ -784,7 +785,8 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
784785
initialWindowSize: 65535, // spec default
785786
initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream,
786787
maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
787-
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
788+
strictMaxConcurrentStreams: conf.StrictMaxConcurrentRequests,
789+
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
788790
streams: make(map[uint32]*clientStream),
789791
singleUse: singleUse,
790792
seenSettingsChan: make(chan struct{}),
@@ -1018,7 +1020,7 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
10181020
return
10191021
}
10201022
var maxConcurrentOkay bool
1021-
if cc.t.StrictMaxConcurrentStreams {
1023+
if cc.strictMaxConcurrentStreams {
10221024
// We'll tell the caller we can take a new request to
10231025
// prevent the caller from dialing a new TCP
10241026
// connection, but then we'll block later before

http2/transport_test.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3475,14 +3475,28 @@ func TestTransportRequestsLowServerLimit(t *testing.T) {
34753475

34763476
// tests Transport.StrictMaxConcurrentStreams
34773477
func TestTransportRequestsStallAtServerLimit(t *testing.T) {
3478-
synctestTest(t, testTransportRequestsStallAtServerLimit)
3478+
synctestSubtest(t, "Transport", func(t testing.TB) {
3479+
testTransportRequestsStallAtServerLimit(t, func(tr *Transport) {
3480+
tr.StrictMaxConcurrentStreams = true
3481+
})
3482+
})
3483+
synctestSubtest(t, "HTTP2Config", func(t testing.TB) {
3484+
// HTTP2Config.StrictMaxConcurrentRequests was added in Go 1.26.
3485+
h2 := &http.HTTP2Config{}
3486+
v := reflect.ValueOf(h2).Elem().FieldByName("StrictMaxConcurrentRequests")
3487+
if !v.IsValid() {
3488+
t.Skip("HTTP2Config does not contain StrictMaxConcurrentRequests")
3489+
}
3490+
v.SetBool(true)
3491+
testTransportRequestsStallAtServerLimit(t, func(tr *http.Transport) {
3492+
tr.HTTP2 = h2
3493+
})
3494+
})
34793495
}
3480-
func testTransportRequestsStallAtServerLimit(t testing.TB) {
3496+
func testTransportRequestsStallAtServerLimit(t testing.TB, opt any) {
34813497
const maxConcurrent = 2
34823498

3483-
tc := newTestClientConn(t, func(tr *Transport) {
3484-
tr.StrictMaxConcurrentStreams = true
3485-
})
3499+
tc := newTestClientConn(t, opt)
34863500
tc.greet(Setting{SettingMaxConcurrentStreams, maxConcurrent})
34873501

34883502
cancelClientRequest := make(chan struct{})

0 commit comments

Comments
 (0)