// Copyright 2026 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package httpcommonimport ()varerrConcurrentRead = errors.New("http: concurrent read on response body")// incomparable is a zero-width, non-comparable type. Adding it to a struct// makes that struct also non-comparable, and generally doesn't add// any size (as long as it's first).typeincomparable [0]func()// GzipReader wraps a response body so it can lazily// get gzip.Reader from the pool on the first call to Read.// After Close is called it puts gzip.Reader to the pool immediately// if there is no Read in progress or later when Read completes.typeGzipReaderstruct { _ incomparableBodyio.ReadCloser// underlying Response.Bodymusync.Mutex// guards zr and zerrzr *gzip.Reader// stores gzip reader from the pool between readszerrerror// sticky gzip reader init error or sentinel value to detect concurrent read and read after close}typeeofReaderstruct{}func (eofReader) ([]byte) (int, error) { return0, io.EOF }func (eofReader) () (byte, error) { return0, io.EOF }vargzipPool = sync.Pool{New: func() any { returnnew(gzip.Reader) }}// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.func ( io.Reader) (*gzip.Reader, error) { := gzipPool.Get().(*gzip.Reader)if := .Reset(); != nil {gzipPoolPut()returnnil, }return , nil}// gzipPoolPut puts a gzip.Reader back into the pool.func ( *gzip.Reader) {// Reset will allocate bufio.Reader if we pass it anything // other than a flate.Reader, so ensure that it's getting one.varflate.Reader = eofReader{} .Reset()gzipPool.Put()}// acquire returns a gzip.Reader for reading response body.// The reader must be released after use.func ( *GzipReader) () (*gzip.Reader, error) { .mu.Lock()defer .mu.Unlock()if .zerr != nil {returnnil, .zerr }if .zr == nil {// gzipPoolGet might block indefinitely since it reads the gzip header. // Therefore, drop mu temporarily when using gzipPoolGet. // We set zerr to errConcurrentRead to prevent concurrent read // even when mu is temporarily dropped. .zerr = errConcurrentRead .mu.Unlock() , := gzipPoolGet(.Body) .mu.Lock()// Guard against Close being called while gzipPoolGet is running.if .zerr != errConcurrentRead {if != nil {gzipPoolPut() }returnnil, .zerr } .zr, .zerr = , if .zerr != nil {returnnil, .zerr } } := .zr .zr, .zerr = nil, errConcurrentReadreturn , nil}// release returns the gzip.Reader to the pool if Close was called during Read.func ( *GzipReader) ( *gzip.Reader) { .mu.Lock()defer .mu.Unlock()if .zerr == errConcurrentRead { .zr, .zerr = , nil } else { // fs.ErrClosedgzipPoolPut() }}// close returns the gzip.Reader to the pool immediately or// signals release to do so after Read completes.func ( *GzipReader) () { .mu.Lock()defer .mu.Unlock()if .zerr == nil && .zr != nil {gzipPoolPut(.zr) .zr = nil } .zerr = fs.ErrClosed}func ( *GzipReader) ( []byte) ( int, error) { , := .acquire()if != nil {return0, }defer .release()return .Read()}func ( *GzipReader) () error { .close()return .Body.Close()}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)