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 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

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"
@@ -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)

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