Skip to content

Commit cf5ad00

Browse files
net/http: pool transport gzip readers
name old time/op new time/op delta GzipReaderInit-8 8.97µs ± 6% 2.61µs ± 7% -70.87% (p=0.000 n=10+9) name old alloc/op new alloc/op delta GzipReaderInit-8 45.5kB ± 0% 4.4kB ± 0% -90.36% (p=0.000 n=10+10) name old allocs/op new allocs/op delta GzipReaderInit-8 10.0 ± 0% 6.0 ± 0% -40.00% (p=0.000 n=10+10) Allocation saving comes from absent compress/flate.(*dictDecoder).init Fixes #61353
1 parent ce0f28e commit cf5ad00

File tree

1 file changed

+64
-16
lines changed

1 file changed

+64
-16
lines changed

src/net/http/transport.go

+64-16
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"
@@ -2844,37 +2845,84 @@ func (es *bodyEOFSignal) condfn(err error) error {
28442845
}
28452846

28462847
// gzipReader wraps a response body so it can lazily
2847-
// call gzip.NewReader on the first call to Read
2848+
// get gzip.Reader from the pool on the first call to Read.
2849+
// After Close is called it puts gzip.Reader to the pool immediately
2850+
// if there is no Read in progress or later when Read completes.
28482851
type gzipReader struct {
28492852
_ incomparable
28502853
body *bodyEOFSignal // underlying HTTP/1 response body framing
2851-
zr *gzip.Reader // lazily-initialized gzip reader
2852-
zerr error // any error from gzip.NewReader; sticky
2854+
2855+
mu sync.Mutex
2856+
zr *gzip.Reader // lazily-initialized gzip reader
2857+
zerr error // any error from gzip.Reader.Reset; sticky
2858+
reading bool // true if Read() is in progress
2859+
closed bool // true if Close() was called
28532860
}
28542861

2855-
func (gz *gzipReader) Read(p []byte) (n int, err error) {
2856-
if gz.zr == nil {
2857-
if gz.zerr == nil {
2858-
gz.zr, gz.zerr = gzip.NewReader(gz.body)
2859-
}
2860-
if gz.zerr != nil {
2861-
return 0, gz.zerr
2862-
}
2863-
}
2862+
type eofReader struct{}
2863+
2864+
func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }
2865+
func (eofReader) ReadByte() (byte, error) { return 0, io.EOF }
28642866

2865-
gz.body.mu.Lock()
2866-
if gz.body.closed {
2867+
var (
2868+
gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
2869+
2870+
// parkedReader used to reset gzip.Reader before returning into the gzipPool as
2871+
// gzip.Reader.Reset(flate.Reader) avoids calling bufio.NewReader
2872+
parkedReader flate.Reader = eofReader{}
2873+
)
2874+
2875+
func gzipPoolPut(zr *gzip.Reader) {
2876+
_ = zr.Reset(parkedReader)
2877+
gzipPool.Put(zr)
2878+
}
2879+
2880+
func (gz *gzipReader) Read(p []byte) (n int, err error) {
2881+
gz.mu.Lock()
2882+
if gz.closed {
28672883
err = errReadOnClosedResBody
2884+
} else {
2885+
if gz.zr == nil {
2886+
if gz.zerr == nil {
2887+
zr := gzipPool.Get().(*gzip.Reader)
2888+
if gz.zerr = zr.Reset(gz.body); gz.zerr == nil {
2889+
gz.zr = zr
2890+
} else {
2891+
gzipPoolPut(zr)
2892+
}
2893+
}
2894+
err = gz.zerr
2895+
}
28682896
}
2869-
gz.body.mu.Unlock()
2897+
gz.reading = err == nil
2898+
gz.mu.Unlock()
28702899

28712900
if err != nil {
28722901
return 0, err
28732902
}
2874-
return gz.zr.Read(p)
2903+
2904+
n, err = gz.zr.Read(p)
2905+
2906+
gz.mu.Lock()
2907+
if gz.closed {
2908+
gzipPoolPut(gz.zr)
2909+
gz.zr = nil
2910+
}
2911+
gz.reading = false
2912+
gz.mu.Unlock()
2913+
2914+
return
28752915
}
28762916

28772917
func (gz *gzipReader) Close() error {
2918+
gz.mu.Lock()
2919+
if !gz.closed && !gz.reading && gz.zr != nil {
2920+
gzipPoolPut(gz.zr)
2921+
gz.zr = nil
2922+
}
2923+
gz.closed = true
2924+
gz.mu.Unlock()
2925+
28782926
return gz.body.Close()
28792927
}
28802928

0 commit comments

Comments
 (0)