Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5d8d6b3

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 15d20b2 commit 5d8d6b3

File tree

1 file changed

+76
-13
lines changed

1 file changed

+76
-13
lines changed

‎src/net/http/transport.go

Lines changed: 76 additions & 13 deletions
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"
@@ -2988,6 +2989,7 @@ type bodyEOFSignal struct {
29882989
}
29892990

29902991
var errReadOnClosedResBody = errors.New("http: read on closed response body")
2992+
var errConcurrentReadOnResBody = errors.New("http: concurrent read on response body")
29912993

29922994
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
29932995
es.mu.Lock()
@@ -3037,37 +3039,98 @@ func (es *bodyEOFSignal) condfn(err error) error {
30373039
}
30383040

30393041
// gzipReader wraps a response body so it can lazily
3040-
// call gzip.NewReader on the first call to Read
3042+
// get gzip.Reader from the pool on the first call to Read.
3043+
// After Close is called it puts gzip.Reader to the pool immediately
3044+
// if there is no Read in progress or later when Read completes.
30413045
type gzipReader struct {
30423046
_ incomparable
30433047
body *bodyEOFSignal // underlying HTTP/1 response body framing
3044-
zr *gzip.Reader // lazily-initialized gzip reader
3045-
zerr error // any error from gzip.NewReader; sticky
3048+
mu sync.Mutex // guards zr and zerr
3049+
zr *gzip.Reader
3050+
zerr error
30463051
}
30473052

3048-
func (gz *gzipReader) Read(p []byte) (n int, err error) {
3053+
type eofReader struct{}
3054+
3055+
func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }
3056+
func (eofReader) ReadByte() (byte, error) { return 0, io.EOF }
3057+
3058+
var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
3059+
3060+
// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.
3061+
func gzipPoolGet(r io.Reader) (*gzip.Reader, error) {
3062+
zr := gzipPool.Get().(*gzip.Reader)
3063+
if err := zr.Reset(r); err != nil {
3064+
gzipPoolPut(zr)
3065+
return nil, err
3066+
}
3067+
return zr, nil
3068+
}
3069+
3070+
// gzipPoolPut puts a gzip.Reader back into the pool.
3071+
func gzipPoolPut(zr *gzip.Reader) {
3072+
// Reset will allocate bufio.Reader if we pass it anything
3073+
// other than a flate.Reader, so ensure that it's getting one.
3074+
var r flate.Reader = eofReader{}
3075+
zr.Reset(r)
3076+
gzipPool.Put(zr)
3077+
}
3078+
3079+
// acquire returns a gzip.Reader for reading response body.
3080+
// The reader must be released after use.
3081+
func (gz *gzipReader) acquire() (*gzip.Reader, error) {
3082+
gz.mu.Lock()
3083+
defer gz.mu.Unlock()
3084+
if gz.zerr != nil {
3085+
return nil, gz.zerr
3086+
}
30493087
if gz.zr == nil {
3050-
if gz.zerr == nil {
3051-
gz.zr, gz.zerr = gzip.NewReader(gz.body)
3052-
}
3088+
gz.zr, gz.zerr = gzipPoolGet(gz.body)
30533089
if gz.zerr != nil {
3054-
return 0, gz.zerr
3090+
return nil, gz.zerr
30553091
}
30563092
}
3093+
ret := gz.zr
3094+
gz.zr, gz.zerr = nil, errConcurrentReadOnResBody
3095+
return ret, nil
3096+
}
3097+
3098+
// release returns the gzip.Reader to the pool if Close was called during Read.
3099+
func (gz *gzipReader) release(zr *gzip.Reader) {
3100+
gz.mu.Lock()
3101+
defer gz.mu.Unlock()
3102+
if gz.zerr == errConcurrentReadOnResBody {
3103+
gz.zr, gz.zerr = zr, nil
3104+
} else { // errReadOnClosedResBody
3105+
gzipPoolPut(zr)
3106+
}
3107+
}
30573108

3058-
gz.body.mu.Lock()
3059-
if gz.body.closed {
3060-
err = errReadOnClosedResBody
3109+
// close returns the gzip.Reader to the pool immediately or
3110+
// signals release to do so after Read completes.
3111+
func (gz *gzipReader) close() {
3112+
gz.mu.Lock()
3113+
defer gz.mu.Unlock()
3114+
if gz.zerr == nil && gz.zr != nil {
3115+
gzipPoolPut(gz.zr)
3116+
gz.zr = nil
30613117
}
3062-
gz.body.mu.Unlock()
3118+
gz.zerr = errReadOnClosedResBody
3119+
}
30633120

3121+
func (gz *gzipReader) Read(p []byte) (n int, err error) {
3122+
zr, err := gz.acquire()
30643123
if err != nil {
30653124
return 0, err
30663125
}
3067-
return gz.zr.Read(p)
3126+
defer gz.release(zr)
3127+
3128+
return zr.Read(p)
30683129
}
30693130

30703131
func (gz *gzipReader) Close() error {
3132+
gz.close()
3133+
30713134
return gz.body.Close()
30723135
}
30733136

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /