Skip to content

Commit 5627bb0

Browse files
bradfitzdmitshur
authored andcommitted
[internal-branch.go1.17-vendor] http2: reduce frameScratchBuffer caching aggressiveness
Use a sync.Pool, not per ClientConn. Co-authored with [email protected] Updates golang/go#49077 Change-Id: I93f06b19857ab495ffcf15d7ed2aaa2a6cb31515 Reviewed-on: https://go-review.googlesource.com/c/net/+/325169 Run-TryBot: Brad Fitzpatrick <[email protected]> Trust: Brad Fitzpatrick <[email protected]> Reviewed-by: Damien Neil <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/net/+/357670 Trust: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent c9f4fb0 commit 5627bb0

File tree

1 file changed

+46
-51
lines changed

1 file changed

+46
-51
lines changed

http2/transport.go

+46-51
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,8 @@ type ClientConn struct {
264264
peerMaxHeaderListSize uint64
265265
initialWindowSize uint32
266266

267-
hbuf bytes.Buffer // HPACK encoder writes into this
268-
henc *hpack.Encoder
269-
freeBuf [][]byte
267+
hbuf bytes.Buffer // HPACK encoder writes into this
268+
henc *hpack.Encoder
270269

271270
wmu sync.Mutex // held while writing; acquire AFTER mu if holding both
272271
werr error // first write error that has occurred
@@ -913,46 +912,6 @@ func (cc *ClientConn) closeForLostPing() error {
913912
return cc.closeForError(err)
914913
}
915914

916-
const maxAllocFrameSize = 512 << 10
917-
918-
// frameBuffer returns a scratch buffer suitable for writing DATA frames.
919-
// They're capped at the min of the peer's max frame size or 512KB
920-
// (kinda arbitrarily), but definitely capped so we don't allocate 4GB
921-
// bufers.
922-
func (cc *ClientConn) frameScratchBuffer() []byte {
923-
cc.mu.Lock()
924-
size := cc.maxFrameSize
925-
if size > maxAllocFrameSize {
926-
size = maxAllocFrameSize
927-
}
928-
for i, buf := range cc.freeBuf {
929-
if len(buf) >= int(size) {
930-
cc.freeBuf[i] = nil
931-
cc.mu.Unlock()
932-
return buf[:size]
933-
}
934-
}
935-
cc.mu.Unlock()
936-
return make([]byte, size)
937-
}
938-
939-
func (cc *ClientConn) putFrameScratchBuffer(buf []byte) {
940-
cc.mu.Lock()
941-
defer cc.mu.Unlock()
942-
const maxBufs = 4 // arbitrary; 4 concurrent requests per conn? investigate.
943-
if len(cc.freeBuf) < maxBufs {
944-
cc.freeBuf = append(cc.freeBuf, buf)
945-
return
946-
}
947-
for i, old := range cc.freeBuf {
948-
if old == nil {
949-
cc.freeBuf[i] = buf
950-
return
951-
}
952-
}
953-
// forget about it.
954-
}
955-
956915
// errRequestCanceled is a copy of net/http's errRequestCanceled because it's not
957916
// exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests.
958917
var errRequestCanceled = errors.New("net/http: request canceled")
@@ -1295,11 +1254,35 @@ var (
12951254
errReqBodyTooLong = errors.New("http2: request body larger than specified content length")
12961255
)
12971256

1257+
// frameScratchBufferLen returns the length of a buffer to use for
1258+
// outgoing request bodies to read/write to/from.
1259+
//
1260+
// It returns max(1, min(peer's advertised max frame size,
1261+
// Request.ContentLength+1, 512KB)).
1262+
func (cs *clientStream) frameScratchBufferLen(maxFrameSize int) int {
1263+
const max = 512 << 10
1264+
n := int64(maxFrameSize)
1265+
if n > max {
1266+
n = max
1267+
}
1268+
if cl := actualContentLength(cs.req); cl != -1 && cl+1 < n {
1269+
// Add an extra byte past the declared content-length to
1270+
// give the caller's Request.Body io.Reader a chance to
1271+
// give us more bytes than they declared, so we can catch it
1272+
// early.
1273+
n = cl + 1
1274+
}
1275+
if n < 1 {
1276+
return 1
1277+
}
1278+
return int(n) // doesn't truncate; max is 512K
1279+
}
1280+
1281+
var bufPool sync.Pool // of *[]byte
1282+
12981283
func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (err error) {
12991284
cc := cs.cc
13001285
sentEnd := false // whether we sent the final DATA frame w/ END_STREAM
1301-
buf := cc.frameScratchBuffer()
1302-
defer cc.putFrameScratchBuffer(buf)
13031286

13041287
defer func() {
13051288
traceWroteRequest(cs.trace, err)
@@ -1318,9 +1301,24 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
13181301
remainLen := actualContentLength(req)
13191302
hasContentLen := remainLen != -1
13201303

1304+
cc.mu.Lock()
1305+
maxFrameSize := int(cc.maxFrameSize)
1306+
cc.mu.Unlock()
1307+
1308+
// Scratch buffer for reading into & writing from.
1309+
scratchLen := cs.frameScratchBufferLen(maxFrameSize)
1310+
var buf []byte
1311+
if bp, ok := bufPool.Get().(*[]byte); ok && len(*bp) >= scratchLen {
1312+
defer bufPool.Put(bp)
1313+
buf = *bp
1314+
} else {
1315+
buf = make([]byte, scratchLen)
1316+
defer bufPool.Put(&buf)
1317+
}
1318+
13211319
var sawEOF bool
13221320
for !sawEOF {
1323-
n, err := body.Read(buf[:len(buf)-1])
1321+
n, err := body.Read(buf[:len(buf)])
13241322
if hasContentLen {
13251323
remainLen -= int64(n)
13261324
if remainLen == 0 && err == nil {
@@ -1331,8 +1329,9 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
13311329
// to send the END_STREAM bit early, double-check that we're actually
13321330
// at EOF. Subsequent reads should return (0, EOF) at this point.
13331331
// If either value is different, we return an error in one of two ways below.
1332+
var scratch [1]byte
13341333
var n1 int
1335-
n1, err = body.Read(buf[n:])
1334+
n1, err = body.Read(scratch[:])
13361335
remainLen -= int64(n1)
13371336
}
13381337
if remainLen < 0 {
@@ -1402,10 +1401,6 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
14021401
}
14031402
}
14041403

1405-
cc.mu.Lock()
1406-
maxFrameSize := int(cc.maxFrameSize)
1407-
cc.mu.Unlock()
1408-
14091404
cc.wmu.Lock()
14101405
defer cc.wmu.Unlock()
14111406

0 commit comments

Comments
 (0)