Skip to content

Commit 2aaa1ad

Browse files
João Miguel Forte Oliveirinhajoliveirinha
João Miguel Forte Oliveirinha
authored andcommitted
http2: add support to configure transport flow control values
Adds to the transport configuration the capability to configure the maximum flow control values instead of using default ones. For applications that use a lot of client connections, being able to configure these values allows for better control on memory usage, particulary when connections are long-lived. related: golang/go#20448
1 parent 689bbc7 commit 2aaa1ad

File tree

2 files changed

+78
-26
lines changed

2 files changed

+78
-26
lines changed

http2/transport.go

+77-25
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,17 @@ import (
3939
)
4040

4141
const (
42-
// transportDefaultConnFlow is how many connection-level flow control
42+
// maxWriteFrameSize is the maximum possible frame size used to write to server.
43+
maxWriteFrameSize = 512 << 10
44+
45+
// defaultTransportDefaultConnFlow is how many connection-level flow control
4346
// tokens we give the server at start-up, past the default 64k.
44-
transportDefaultConnFlow = 1 << 30
47+
defaultTransportDefaultConnFlow = 1 << 30
4548

46-
// transportDefaultStreamFlow is how many stream-level flow
49+
// defaultTransportDefaultStreamFlow is how many stream-level flow
4750
// control tokens we announce to the peer, and how many bytes
4851
// we buffer per stream.
49-
transportDefaultStreamFlow = 4 << 20
52+
defaultTransportDefaultStreamFlow = 4 << 20
5053

5154
defaultUserAgent = "Go-http-client/2.0"
5255

@@ -124,6 +127,21 @@ type Transport struct {
124127
// Values are bounded in the range 16k to 16M.
125128
MaxReadFrameSize uint32
126129

130+
// MaxWriteFrameSize is the maximum frame size that we can a client
131+
// connection can send to a server, even if server advertises a higher value.
132+
// If 0, then a default value is used.
133+
MaxWriteFrameSize uint32
134+
135+
// MaxDownloadBufferPerConnection is the maximum per connection buffer size,
136+
// used for receiving data from the server.
137+
// If 0, then a default value is used.
138+
MaxDownloadBufferPerConnection uint32
139+
140+
// MaxDownloadBufferPerStream is the maximum buffer to use for inflow data sent
141+
// by the server.
142+
// If 0, then a default value is used.
143+
MaxDownloadBufferPerStream uint32
144+
127145
// MaxDecoderHeaderTableSize optionally specifies the http2
128146
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
129147
// informs the remote endpoint of the maximum size of the header compression
@@ -304,6 +322,9 @@ type ClientConn struct {
304322
idleTimeout time.Duration // or 0 for never
305323
idleTimer *time.Timer
306324

325+
maxWriteFrameSize uint32
326+
maxDownloadBufferPerStream uint32
327+
307328
mu sync.Mutex // guards following
308329
cond *sync.Cond // hold mu; broadcast on flow/closed changes
309330
flow outflow // our conn-level flow control quota (cs.outflow is per stream)
@@ -731,25 +752,55 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
731752
return initialHeaderTableSize
732753
}
733754

755+
func (t *Transport) maxDownloadBufferPerConnection() uint32 {
756+
maxWindow := uint32((2 << 31) - 1 - initialWindowSize)
757+
758+
if v := t.MaxDownloadBufferPerConnection; v >= initialWindowSize {
759+
if v > maxWindow {
760+
return maxWindow
761+
} else {
762+
return v
763+
}
764+
}
765+
766+
return defaultTransportDefaultConnFlow
767+
}
768+
769+
func (t *Transport) maxDownloadBufferPerStream() uint32 {
770+
if v := t.MaxDownloadBufferPerStream; v > 0 {
771+
return v
772+
}
773+
return defaultTransportDefaultStreamFlow
774+
}
775+
776+
func (t *Transport) maxWriteFrameSize() uint32 {
777+
if v := t.MaxWriteFrameSize; v > 0 && v <= maxWriteFrameSize {
778+
return v
779+
}
780+
return maxWriteFrameSize
781+
}
782+
734783
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
735784
return t.newClientConn(c, t.disableKeepAlives())
736785
}
737786

738787
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
739788
cc := &ClientConn{
740-
t: t,
741-
tconn: c,
742-
readerDone: make(chan struct{}),
743-
nextStreamID: 1,
744-
maxFrameSize: 16 << 10, // spec default
745-
initialWindowSize: 65535, // spec default
746-
maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
747-
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
748-
streams: make(map[uint32]*clientStream),
749-
singleUse: singleUse,
750-
wantSettingsAck: true,
751-
pings: make(map[[8]byte]chan struct{}),
752-
reqHeaderMu: make(chan struct{}, 1),
789+
t: t,
790+
tconn: c,
791+
readerDone: make(chan struct{}),
792+
nextStreamID: 1,
793+
maxFrameSize: 16 << 10, // spec default
794+
initialWindowSize: 65535, // spec default
795+
maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
796+
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
797+
maxWriteFrameSize: t.maxWriteFrameSize(),
798+
maxDownloadBufferPerStream: t.maxDownloadBufferPerStream(),
799+
streams: make(map[uint32]*clientStream),
800+
singleUse: singleUse,
801+
wantSettingsAck: true,
802+
pings: make(map[[8]byte]chan struct{}),
803+
reqHeaderMu: make(chan struct{}, 1),
753804
}
754805
if d := t.idleConnTimeout(); d != 0 {
755806
cc.idleTimeout = d
@@ -796,7 +847,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
796847

797848
initialSettings := []Setting{
798849
{ID: SettingEnablePush, Val: 0},
799-
{ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow},
850+
{ID: SettingInitialWindowSize, Val: t.maxDownloadBufferPerStream()},
800851
}
801852
if max := t.maxFrameReadSize(); max != 0 {
802853
initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: max})
@@ -810,8 +861,8 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
810861

811862
cc.bw.Write(clientPreface)
812863
cc.fr.WriteSettings(initialSettings...)
813-
cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow)
814-
cc.inflow.init(transportDefaultConnFlow + initialWindowSize)
864+
cc.fr.WriteWindowUpdate(0, t.maxDownloadBufferPerConnection())
865+
cc.inflow.init(int32(t.maxDownloadBufferPerConnection()) + initialWindowSize)
815866
cc.bw.Flush()
816867
if cc.werr != nil {
817868
cc.Close()
@@ -1660,12 +1711,12 @@ var (
16601711
// outgoing request bodies to read/write to/from.
16611712
//
16621713
// It returns max(1, min(peer's advertised max frame size,
1663-
// Request.ContentLength+1, 512KB)).
1714+
// Request.ContentLength+1, Transport.MaxWriteFrameSize)).
16641715
func (cs *clientStream) frameScratchBufferLen(maxFrameSize int) int {
1665-
const max = 512 << 10
1716+
var maxSize = int64(cs.cc.maxWriteFrameSize)
16661717
n := int64(maxFrameSize)
1667-
if n > max {
1668-
n = max
1718+
if n > maxSize {
1719+
n = maxSize
16691720
}
16701721
if cl := cs.reqBodyContentLength; cl != -1 && cl+1 < n {
16711722
// Add an extra byte past the declared content-length to
@@ -2120,7 +2171,8 @@ type resAndError struct {
21202171
func (cc *ClientConn) addStreamLocked(cs *clientStream) {
21212172
cs.flow.add(int32(cc.initialWindowSize))
21222173
cs.flow.setConnFlow(&cc.flow)
2123-
cs.inflow.init(transportDefaultStreamFlow)
2174+
// no need to truncate since max is maxWriteFrameSize
2175+
cs.inflow.init(int32(cc.maxDownloadBufferPerStream))
21242176
cs.ID = cc.nextStreamID
21252177
cc.nextStreamID += 2
21262178
cc.streams[cs.ID] = cs

http2/transport_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2867,7 +2867,7 @@ func TestTransportFlowControl(t *testing.T) {
28672867
}
28682868
read += int64(n)
28692869

2870-
const max = transportDefaultStreamFlow
2870+
const max = defaultTransportDefaultStreamFlow
28712871
if w := atomic.LoadInt64(&wrote); -max > read-w || read-w > max {
28722872
t.Fatalf("Too much data inflight: server wrote %v bytes but client only received %v", w, read)
28732873
}

0 commit comments

Comments
 (0)