Skip to content

Commit bb4ce86

Browse files
bradfitzdmitshur
authored andcommitted
[internal-branch.go1.16-vendor] http2: reduce frameScratchBuffer caching aggressiveness
Use a sync.Pool, not per ClientConn. Co-authored with [email protected] Updates golang/go#49076 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/+/356975 Trust: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 3112343 commit bb4ce86

File tree

1 file changed

+46
-51
lines changed

1 file changed

+46
-51
lines changed

http2/transport.go

Lines changed: 46 additions & 51 deletions
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
@@ -917,46 +916,6 @@ func (cc *ClientConn) closeForLostPing() error {
917916
return cc.closeForError(err)
918917
}
919918

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

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

13081291
defer func() {
13091292
traceWroteRequest(cs.trace, err)
@@ -1322,9 +1305,24 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
13221305
remainLen := actualContentLength(req)
13231306
hasContentLen := remainLen != -1
13241307

1308+
cc.mu.Lock()
1309+
maxFrameSize := int(cc.maxFrameSize)
1310+
cc.mu.Unlock()
1311+
1312+
// Scratch buffer for reading into & writing from.
1313+
scratchLen := cs.frameScratchBufferLen(maxFrameSize)
1314+
var buf []byte
1315+
if bp, ok := bufPool.Get().(*[]byte); ok && len(*bp) >= scratchLen {
1316+
defer bufPool.Put(bp)
1317+
buf = *bp
1318+
} else {
1319+
buf = make([]byte, scratchLen)
1320+
defer bufPool.Put(&buf)
1321+
}
1322+
13251323
var sawEOF bool
13261324
for !sawEOF {
1327-
n, err := body.Read(buf[:len(buf)-1])
1325+
n, err := body.Read(buf[:len(buf)])
13281326
if hasContentLen {
13291327
remainLen -= int64(n)
13301328
if remainLen == 0 && err == nil {
@@ -1335,8 +1333,9 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
13351333
// to send the END_STREAM bit early, double-check that we're actually
13361334
// at EOF. Subsequent reads should return (0, EOF) at this point.
13371335
// If either value is different, we return an error in one of two ways below.
1336+
var scratch [1]byte
13381337
var n1 int
1339-
n1, err = body.Read(buf[n:])
1338+
n1, err = body.Read(scratch[:])
13401339
remainLen -= int64(n1)
13411340
}
13421341
if remainLen < 0 {
@@ -1406,10 +1405,6 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
14061405
}
14071406
}
14081407

1409-
cc.mu.Lock()
1410-
maxFrameSize := int(cc.maxFrameSize)
1411-
cc.mu.Unlock()
1412-
14131408
cc.wmu.Lock()
14141409
defer cc.wmu.Unlock()
14151410

0 commit comments

Comments
 (0)