From 411338c97a88c34c451c0ed4a96d25c61816c061 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Tue, 21 Apr 2026 10:31:56 -0700 Subject: [PATCH 1/2] fix: return currentBuffer and prevTail to dstPool in init to stop leak Writer.init unconditionally cleared z.currentBuffer and z.prevTail to nil, which on a Reset/Close cycle dropped a fully-allocated buffer and an optional tail buffer on the floor every iteration. Both had just been fetched from z.dstPool on the preceding Close path (compressCurrent at gzip.go:272 / Close calling compressCurrent with flush=true), so the pool never saw them again. Long-running hot loops that reuse a single Writer with Reset(buf); Write; Close; ... grew allocations without bound as the sync.Pool hit-rate collapsed (#56). Put both buffers back before zeroing them. The existing 'Put in .compressBlock' and 'Put p / Put prevTail' call sites show the pattern already in use for the other Get locations - this change just extends it to the init path so the pool is complete. Behavioural note: init runs only from NewWriter and from Reset (via SetConcurrency). In NewWriter the fields are the zero-value nil, the new guards short-circuit, and nothing changes. Only the Reset path benefits, which is the path that users complained about. Closes #56 Signed-off-by: SAY-5 --- gzip.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gzip.go b/gzip.go index 14124b9..6cc57d6 100644 --- a/gzip.go +++ b/gzip.go @@ -155,9 +155,19 @@ func (z *Writer) init(w io.Writer, level int) { z.Extra = nil z.ModTime = time.Time{} z.wroteHeader = false - z.currentBuffer = nil + // Return any live buffer back to the pool before clearing it so + // repeated NewWriter/Close cycles via init(...) stop leaking one + // dstPool slot per iteration (#56). Same for prevTail, which init + // used to drop on the floor. + if z.currentBuffer != nil { + z.dstPool.Put(z.currentBuffer) + z.currentBuffer = nil + } z.buf = [10]byte{} - z.prevTail = nil + if z.prevTail != nil { + z.dstPool.Put(z.prevTail) + z.prevTail = nil + } z.size = 0 if z.dictFlatePool.New == nil { z.dictFlatePool.New = func() any { From 95d04aa8e54e7144291f846bf1c08acf6182d65d Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 22 Apr 2026 10:33:21 +0200 Subject: [PATCH 2/2] Apply suggestion from @klauspost No need to describe what can easily be seen in code. --- gzip.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gzip.go b/gzip.go index 6cc57d6..ef75ceb 100644 --- a/gzip.go +++ b/gzip.go @@ -155,10 +155,6 @@ func (z *Writer) init(w io.Writer, level int) { z.Extra = nil z.ModTime = time.Time{} z.wroteHeader = false - // Return any live buffer back to the pool before clearing it so - // repeated NewWriter/Close cycles via init(...) stop leaking one - // dstPool slot per iteration (#56). Same for prevTail, which init - // used to drop on the floor. if z.currentBuffer != nil { z.dstPool.Put(z.currentBuffer) z.currentBuffer = nil