fix(pools): return pooled gzip reader on Reset error

GetGzipReader got a reader from the pool, Reset it, and on error returned a
fresh gzip.NewReader while never returning the pooled reader to the pool —
one leaked reader per reset error (non-gzip input), silently draining the
pool. Return the pooled reader without Close (Close panics because the
decompressor is nil until a header parses); the next caller's Reset
reinitialises it.

Test: TestGetGzipReader_ResetErrorPath (error path leaves the pool usable
for subsequent valid gzip reads).
This commit is contained in:
2026-08-13 03:10:21 +01:00
parent bbd43bfe9e
commit 546ded1d9c
2 changed files with 34 additions and 1 deletions
+6 -1
View File
@@ -78,7 +78,12 @@ func PutGzipWriter(gz *gzip.Writer) {
func GetGzipReader(r io.Reader) (*gzip.Reader, error) { func GetGzipReader(r io.Reader) (*gzip.Reader, error) {
gr := gzipReaderPool.Get().(*gzip.Reader) gr := gzipReaderPool.Get().(*gzip.Reader)
if err := gr.Reset(r); err != nil { if err := gr.Reset(r); err != nil {
// If reset fails, create a new reader // Reset failed (e.g. non-gzip data): return the pooled reader so it
// isn't leaked, and hand back a fresh one for this call. We must not
// call gr.Close() here — the decompressor is nil until a header
// parses, so Close would panic. The next GetGzipReader's Reset
// reinitialises it.
gzipReaderPool.Put(gr)
return gzip.NewReader(r) return gzip.NewReader(r)
} }
return gr, nil return gr, nil
+28
View File
@@ -415,3 +415,31 @@ func BenchmarkWithoutPool(b *testing.B) {
} }
}) })
} }
// TestGetGzipReader_ResetErrorPath exercises the path where the pooled reader
// fails to Reset (non-gzip input). The pooled reader must be returned to the
// pool and a usable reader handed back; a subsequent valid gzip read must
// still succeed so the pool is not left in a broken state.
func (suite *BufferPoolTestSuite) TestGetGzipReader_ResetErrorPath() {
t := suite.T()
// Non-gzip input -> Reset fails -> fresh reader returned (with error).
gr1, err := GetGzipReader(strings.NewReader("not gzip data at all"))
assert.Error(t, err, "expected error decoding non-gzip data")
if gr1 != nil {
PutGzipReader(gr1)
}
// Valid gzip input afterwards must still work, proving the pool is usable.
var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
_, werr := zw.Write([]byte("hello world"))
assert.NoError(t, werr)
assert.NoError(t, zw.Close())
gr2, err := GetGzipReader(bytes.NewReader(buf.Bytes()))
assert.NoError(t, err)
defer PutGzipReader(gr2)
decoded, rerr := io.ReadAll(gr2)
assert.NoError(t, rerr)
assert.Equal(t, "hello world", string(decoded))
}