mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-11 05:27:47 +00:00
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).
116 lines
2.8 KiB
Go
116 lines
2.8 KiB
Go
// Package pools provides memory-efficient buffer and gzip reader pools
|
|
// for reducing allocations in high-throughput request processing.
|
|
// Buffers are automatically sized and recycled to minimize GC pressure.
|
|
package pools
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
// MaxBufferSize is the maximum size of a buffer that will be returned to the pool
|
|
MaxBufferSize = 1024 * 1024 // 1MB
|
|
// InitialBufferSize is the initial capacity of buffers in the pool
|
|
InitialBufferSize = 4096 // 4KB
|
|
)
|
|
|
|
// bufferPool is the global pool for reusable buffers
|
|
var bufferPool = &sync.Pool{
|
|
New: func() any {
|
|
return bytes.NewBuffer(make([]byte, 0, InitialBufferSize))
|
|
},
|
|
}
|
|
|
|
// gzipWriterPool is the global pool for reusable gzip writers
|
|
var gzipWriterPool = &sync.Pool{
|
|
New: func() any {
|
|
return gzip.NewWriter(nil)
|
|
},
|
|
}
|
|
|
|
// gzipReaderPool is the global pool for reusable gzip readers
|
|
var gzipReaderPool = &sync.Pool{
|
|
New: func() any {
|
|
return new(gzip.Reader)
|
|
},
|
|
}
|
|
|
|
// GetBuffer retrieves a buffer from the pool
|
|
func GetBuffer() *bytes.Buffer {
|
|
buf := bufferPool.Get().(*bytes.Buffer)
|
|
buf.Reset()
|
|
return buf
|
|
}
|
|
|
|
// PutBuffer returns a buffer to the pool
|
|
func PutBuffer(buf *bytes.Buffer) {
|
|
if buf == nil {
|
|
return
|
|
}
|
|
// Don't pool large buffers to avoid memory bloat
|
|
if buf.Cap() > MaxBufferSize {
|
|
return
|
|
}
|
|
buf.Reset()
|
|
bufferPool.Put(buf)
|
|
}
|
|
|
|
// GetGzipWriter retrieves a gzip writer from the pool
|
|
func GetGzipWriter(w io.Writer) *gzip.Writer {
|
|
gz := gzipWriterPool.Get().(*gzip.Writer)
|
|
gz.Reset(w)
|
|
return gz
|
|
}
|
|
|
|
// PutGzipWriter returns a gzip writer to the pool
|
|
func PutGzipWriter(gz *gzip.Writer) {
|
|
if gz == nil {
|
|
return
|
|
}
|
|
gz.Reset(nil)
|
|
gzipWriterPool.Put(gz)
|
|
}
|
|
|
|
// GetGzipReader retrieves a gzip reader from the pool
|
|
func GetGzipReader(r io.Reader) (*gzip.Reader, error) {
|
|
gr := gzipReaderPool.Get().(*gzip.Reader)
|
|
if err := gr.Reset(r); err != nil {
|
|
// 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 gr, nil
|
|
}
|
|
|
|
// PutGzipReader returns a gzip reader to the pool
|
|
func PutGzipReader(gr *gzip.Reader) {
|
|
if gr == nil {
|
|
return
|
|
}
|
|
gr.Close()
|
|
gzipReaderPool.Put(gr)
|
|
}
|
|
|
|
// Stats provides statistics about the buffer pool usage
|
|
type Stats struct {
|
|
BuffersInUse int
|
|
MaxBufferSize int
|
|
}
|
|
|
|
// GetStats returns current pool statistics (placeholder for future monitoring)
|
|
func GetStats() Stats {
|
|
// This is a placeholder for future implementation
|
|
// sync.Pool doesn't provide direct statistics access
|
|
return Stats{
|
|
BuffersInUse: 0,
|
|
MaxBufferSize: MaxBufferSize,
|
|
}
|
|
}
|