Skip to content

Commit e26eb44

Browse files
net/http: pool transport gzip readers
goos: linux goarch: amd64 pkg: net/http │ HEAD~1 │ HEAD │ │ sec/op │ sec/op vs base │ ClientGzip-8 621.0µ ± 2% 616.3µ ± 10% ~ (p=0.971 n=10) │ HEAD~1 │ HEAD │ │ B/op │ B/op vs base │ ClientGzip-8 49.765Ki ± 0% 9.514Ki ± 2% -80.88% (p=0.000 n=10) │ HEAD~1 │ HEAD │ │ allocs/op │ allocs/op vs base │ ClientGzip-8 57.00 ± 0% 52.00 ± 0% -8.77% (p=0.000 n=10) Allocation saving comes from absent compress/flate.(*dictDecoder).init Updates #61353
1 parent 1baf68b commit e26eb44

File tree

1 file changed

+76
-13
lines changed

1 file changed

+76
-13
lines changed

src/net/http/transport.go

+76-13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package http
1111

1212
import (
1313
"bufio"
14+
"compress/flate"
1415
"compress/gzip"
1516
"container/list"
1617
"context"
@@ -2976,6 +2977,7 @@ type bodyEOFSignal struct {
29762977
}
29772978

29782979
var errReadOnClosedResBody = errors.New("http: read on closed response body")
2980+
var errConcurrentReadOnResBody = errors.New("http: concurrent read on response body")
29792981

29802982
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
29812983
es.mu.Lock()
@@ -3025,37 +3027,98 @@ func (es *bodyEOFSignal) condfn(err error) error {
30253027
}
30263028

30273029
// gzipReader wraps a response body so it can lazily
3028-
// call gzip.NewReader on the first call to Read
3030+
// get gzip.Reader from the pool on the first call to Read.
3031+
// After Close is called it puts gzip.Reader to the pool immediately
3032+
// if there is no Read in progress or later when Read completes.
30293033
type gzipReader struct {
30303034
_ incomparable
30313035
body *bodyEOFSignal // underlying HTTP/1 response body framing
3032-
zr *gzip.Reader // lazily-initialized gzip reader
3033-
zerr error // any error from gzip.NewReader; sticky
3036+
mu sync.Mutex // guards zr and zerr
3037+
zr *gzip.Reader
3038+
zerr error
30343039
}
30353040

3036-
func (gz *gzipReader) Read(p []byte) (n int, err error) {
3041+
type eofReader struct{}
3042+
3043+
func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }
3044+
func (eofReader) ReadByte() (byte, error) { return 0, io.EOF }
3045+
3046+
var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
3047+
3048+
// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.
3049+
func gzipPoolGet(r io.Reader) (*gzip.Reader, error) {
3050+
zr := gzipPool.Get().(*gzip.Reader)
3051+
if err := zr.Reset(r); err != nil {
3052+
gzipPoolPut(zr)
3053+
return nil, err
3054+
}
3055+
return zr, nil
3056+
}
3057+
3058+
// gzipPoolPut puts a gzip.Reader back into the pool.
3059+
func gzipPoolPut(zr *gzip.Reader) {
3060+
// Reset will allocate bufio.Reader if we pass it anything
3061+
// other than a flate.Reader, so ensure that it's getting one.
3062+
var r flate.Reader = eofReader{}
3063+
zr.Reset(r)
3064+
gzipPool.Put(zr)
3065+
}
3066+
3067+
// acquire returns a gzip.Reader for reading response body.
3068+
// The reader must be released after use.
3069+
func (gz *gzipReader) acquire() (*gzip.Reader, error) {
3070+
gz.mu.Lock()
3071+
defer gz.mu.Unlock()
3072+
if gz.zerr != nil {
3073+
return nil, gz.zerr
3074+
}
30373075
if gz.zr == nil {
3038-
if gz.zerr == nil {
3039-
gz.zr, gz.zerr = gzip.NewReader(gz.body)
3040-
}
3076+
gz.zr, gz.zerr = gzipPoolGet(gz.body)
30413077
if gz.zerr != nil {
3042-
return 0, gz.zerr
3078+
return nil, gz.zerr
30433079
}
30443080
}
3081+
ret := gz.zr
3082+
gz.zr, gz.zerr = nil, errConcurrentReadOnResBody
3083+
return ret, nil
3084+
}
3085+
3086+
// release returns the gzip.Reader to the pool if Close was called during Read.
3087+
func (gz *gzipReader) release(zr *gzip.Reader) {
3088+
gz.mu.Lock()
3089+
defer gz.mu.Unlock()
3090+
if gz.zerr == errConcurrentReadOnResBody {
3091+
gz.zr, gz.zerr = zr, nil
3092+
} else { // errReadOnClosedResBody
3093+
gzipPoolPut(zr)
3094+
}
3095+
}
30453096

3046-
gz.body.mu.Lock()
3047-
if gz.body.closed {
3048-
err = errReadOnClosedResBody
3097+
// close returns the gzip.Reader to the pool immediately or
3098+
// signals release to do so after Read completes.
3099+
func (gz *gzipReader) close() {
3100+
gz.mu.Lock()
3101+
defer gz.mu.Unlock()
3102+
if gz.zerr == nil && gz.zr != nil {
3103+
gzipPoolPut(gz.zr)
3104+
gz.zr = nil
30493105
}
3050-
gz.body.mu.Unlock()
3106+
gz.zerr = errReadOnClosedResBody
3107+
}
30513108

3109+
func (gz *gzipReader) Read(p []byte) (n int, err error) {
3110+
zr, err := gz.acquire()
30523111
if err != nil {
30533112
return 0, err
30543113
}
3055-
return gz.zr.Read(p)
3114+
defer gz.release(zr)
3115+
3116+
return zr.Read(p)
30563117
}
30573118

30583119
func (gz *gzipReader) Close() error {
3120+
gz.close()
3121+
30593122
return gz.body.Close()
30603123
}
30613124

0 commit comments

Comments
 (0)