Skip to content

Commit f8bed21

Browse files
stevenhianlancetaylor
authored andcommitted
http2: limit client initial MAX_CONCURRENT_STREAMS
Prevent the client trying to establish more streams than the server is willing to accept during the initial life time of a connection by limiting `maxConcurrentStreams` to `100`, the http2 specifications recommended minimum, until we've received the initial `SETTINGS` frame from the server. After a `SETTINGS` frame has been received use the servers `MAX_CONCURRENT_STREAMS`, if present, otherwise use `1000` as a reasonable value. For normal consumers this will have very little impact, allowing a decent level of concurrency from the start, and for highly concurrent consumers or large bursts it will prevent significant number of rejected streams being attempted hence actually increasing performance. Fixes golang/go#39389 Change-Id: I35fecd501ca39cd059c7afd1d44090b023f16e1e GitHub-Last-Rev: 0d1114d GitHub-Pull-Request: #73 Reviewed-on: https://go-review.googlesource.com/c/net/+/236497 Reviewed-by: Brad Fitzpatrick <[email protected]> Trust: Brad Fitzpatrick <[email protected]> Trust: Joe Tsai <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]>
1 parent ad29c8a commit f8bed21

File tree

1 file changed

+27
-4
lines changed

1 file changed

+27
-4
lines changed

http2/transport.go

+27-4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ const (
5151
transportDefaultStreamMinRefresh = 4 << 10
5252

5353
defaultUserAgent = "Go-http-client/2.0"
54+
55+
// initialMaxConcurrentStreams is a connections maxConcurrentStreams until
56+
// it's received servers initial SETTINGS frame, which corresponds with the
57+
// spec's minimum recommended value.
58+
initialMaxConcurrentStreams = 100
59+
60+
// defaultMaxConcurrentStreams is a connections default maxConcurrentStreams
61+
// if the server doesn't include one in its initial SETTINGS frame.
62+
defaultMaxConcurrentStreams = 1000
5463
)
5564

5665
// Transport is an HTTP/2 Transport.
@@ -247,6 +256,7 @@ type ClientConn struct {
247256
doNotReuse bool // whether conn is marked to not be reused for any future requests
248257
closing bool
249258
closed bool
259+
seenSettings bool // true if we've seen a settings frame, false otherwise
250260
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
251261
goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
252262
goAwayDebug string // goAway frame's debug data, retained as a string
@@ -642,10 +652,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
642652
tconn: c,
643653
readerDone: make(chan struct{}),
644654
nextStreamID: 1,
645-
maxFrameSize: 16 << 10, // spec default
646-
initialWindowSize: 65535, // spec default
647-
maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
648-
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
655+
maxFrameSize: 16 << 10, // spec default
656+
initialWindowSize: 65535, // spec default
657+
maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
658+
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
649659
streams: make(map[uint32]*clientStream),
650660
singleUse: singleUse,
651661
wantSettingsAck: true,
@@ -2353,12 +2363,14 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
23532363
return ConnectionError(ErrCodeProtocol)
23542364
}
23552365

2366+
var seenMaxConcurrentStreams bool
23562367
err := f.ForeachSetting(func(s Setting) error {
23572368
switch s.ID {
23582369
case SettingMaxFrameSize:
23592370
cc.maxFrameSize = s.Val
23602371
case SettingMaxConcurrentStreams:
23612372
cc.maxConcurrentStreams = s.Val
2373+
seenMaxConcurrentStreams = true
23622374
case SettingMaxHeaderListSize:
23632375
cc.peerMaxHeaderListSize = uint64(s.Val)
23642376
case SettingInitialWindowSize:
@@ -2390,6 +2402,17 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
23902402
return err
23912403
}
23922404

2405+
if !cc.seenSettings {
2406+
if !seenMaxConcurrentStreams {
2407+
// This was the servers initial SETTINGS frame and it
2408+
// didn't contain a MAX_CONCURRENT_STREAMS field so
2409+
// increase the number of concurrent streams this
2410+
// connection can establish to our default.
2411+
cc.maxConcurrentStreams = defaultMaxConcurrentStreams
2412+
}
2413+
cc.seenSettings = true
2414+
}
2415+
23932416
cc.wmu.Lock()
23942417
defer cc.wmu.Unlock()
23952418

0 commit comments

Comments
 (0)