Files
traefikoidc/internal/pool/pool.go
T
lukaszraczylo ae59a5e88a 0.7.10 (#80)
* Add ability to disable replay protection. - This is useful for runs with multiple traefik replicas to avoid false positives and tokens re-creation.
* Enhance the CI/CD pipelines
* Increase test coverage.
* Update vendored dependencies.
* Update behaviour on forceHTTPS as per issue #82
2025-10-16 10:56:28 +01:00

550 lines
14 KiB
Go

// Package pool provides a unified, centralized memory pool management system
// for the entire application. It consolidates all duplicate pool implementations
// into a single, efficient, and thread-safe package.
package pool
import (
"bytes"
"compress/gzip"
"encoding/json"
"io"
"strings"
"sync"
"sync/atomic"
)
// Manager is the centralized pool manager that consolidates all memory pools
// used throughout the application. It provides a single entry point for
// all pooling operations, reducing duplicate code and improving maintainability.
type Manager struct {
// Buffer pools
smallBufferPool *sync.Pool // 1KB buffers
mediumBufferPool *sync.Pool // 4KB buffers
largeBufferPool *sync.Pool // 8KB buffers
xlBufferPool *sync.Pool // 16KB buffers
// Compression pools
gzipWriterPool *sync.Pool
gzipReaderPool *sync.Pool
// String builder pool
stringBuilderPool *sync.Pool
// JWT parsing buffers
jwtBufferPool *sync.Pool
// HTTP response buffers
httpResponsePool *sync.Pool
// Byte slice pools for various sizes
byteSlicePools map[int]*sync.Pool
poolMu sync.RWMutex
// Statistics
stats PoolStats
}
// PoolStats tracks pool usage statistics
type PoolStats struct {
BufferGets uint64
BufferPuts uint64
GzipGets uint64
GzipPuts uint64
StringGets uint64
StringPuts uint64
JWTGets uint64
JWTPuts uint64
HTTPGets uint64
HTTPPuts uint64
JSONEncoderGets uint64
JSONEncoderPuts uint64
JSONDecoderGets uint64
JSONDecoderPuts uint64
OversizedRejects uint64
}
// JWTBuffer provides pre-allocated buffers for JWT parsing
type JWTBuffer struct {
Header []byte
Payload []byte
Signature []byte
}
var (
// globalManager is the singleton pool manager instance
globalManager *Manager
// managerOnce ensures single initialization
managerOnce sync.Once
)
// Get returns the global pool manager instance
func Get() *Manager {
managerOnce.Do(func() {
globalManager = newManager()
})
return globalManager
}
// newManager creates a new pool manager with all pools initialized
func newManager() *Manager {
m := &Manager{
byteSlicePools: make(map[int]*sync.Pool),
}
// Initialize buffer pools with different sizes
m.smallBufferPool = &sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 1024))
},
}
m.mediumBufferPool = &sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 4096))
},
}
m.largeBufferPool = &sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 8192))
},
}
m.xlBufferPool = &sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 16384))
},
}
// Initialize compression pools
m.gzipWriterPool = &sync.Pool{
New: func() interface{} {
w, _ := gzip.NewWriterLevel(nil, gzip.BestSpeed) // Safe to ignore: factory function
return w
},
}
m.gzipReaderPool = &sync.Pool{
New: func() interface{} {
return (*gzip.Reader)(nil)
},
}
// Initialize string builder pool
m.stringBuilderPool = &sync.Pool{
New: func() interface{} {
sb := &strings.Builder{}
sb.Grow(1024)
return sb
},
}
// Initialize JWT buffer pool
m.jwtBufferPool = &sync.Pool{
New: func() interface{} {
return &JWTBuffer{
Header: make([]byte, 0, 512),
Payload: make([]byte, 0, 2048),
Signature: make([]byte, 0, 512),
}
},
}
// Initialize HTTP response buffer pool
m.httpResponsePool = &sync.Pool{
New: func() interface{} {
buf := make([]byte, 0, 8192)
return &buf
},
}
// Initialize common byte slice pools
for _, size := range []int{256, 512, 1024, 2048, 4096, 8192, 16384} {
size := size // capture for closure
m.byteSlicePools[size] = &sync.Pool{
New: func() interface{} {
b := make([]byte, size)
return &b
},
}
}
return m
}
// GetBuffer returns a buffer from the appropriate pool based on size hint
func (m *Manager) GetBuffer(sizeHint int) *bytes.Buffer {
atomic.AddUint64(&m.stats.BufferGets, 1)
switch {
case sizeHint <= 1024:
buf, _ := m.smallBufferPool.Get().(*bytes.Buffer) // Safe to ignore: pool return is best-effort
return buf
case sizeHint <= 4096:
buf, _ := m.mediumBufferPool.Get().(*bytes.Buffer) // Safe to ignore: pool return is best-effort
return buf
case sizeHint <= 8192:
buf, _ := m.largeBufferPool.Get().(*bytes.Buffer) // Safe to ignore: pool return is best-effort
return buf
case sizeHint <= 16384:
buf, _ := m.xlBufferPool.Get().(*bytes.Buffer) // Safe to ignore: pool return is best-effort
return buf
default:
// For very large buffers, create new ones
return bytes.NewBuffer(make([]byte, 0, sizeHint))
}
}
// PutBuffer returns a buffer to the appropriate pool
func (m *Manager) PutBuffer(buf *bytes.Buffer) {
if buf == nil {
return
}
atomic.AddUint64(&m.stats.BufferPuts, 1)
// Reset buffer before returning to pool
capacity := buf.Cap()
buf.Reset()
// Reject oversized buffers to prevent memory bloat
if capacity > 32768 {
atomic.AddUint64(&m.stats.OversizedRejects, 1)
return
}
// Return to appropriate pool based on capacity
switch {
case capacity <= 1024:
m.smallBufferPool.Put(buf)
case capacity <= 4096:
m.mediumBufferPool.Put(buf)
case capacity <= 8192:
m.largeBufferPool.Put(buf)
case capacity <= 16384:
m.xlBufferPool.Put(buf)
}
}
// GetGzipWriter returns a gzip writer from the pool
func (m *Manager) GetGzipWriter() *gzip.Writer {
atomic.AddUint64(&m.stats.GzipGets, 1)
w, _ := m.gzipWriterPool.Get().(*gzip.Writer) // Safe to ignore: pool return is best-effort
return w
}
// PutGzipWriter returns a gzip writer to the pool
func (m *Manager) PutGzipWriter(w *gzip.Writer) {
if w == nil {
return
}
atomic.AddUint64(&m.stats.GzipPuts, 1)
w.Reset(nil)
m.gzipWriterPool.Put(w)
}
// GetGzipReader returns a gzip reader from the pool
func (m *Manager) GetGzipReader() *gzip.Reader {
atomic.AddUint64(&m.stats.GzipGets, 1)
r := m.gzipReaderPool.Get()
if r == nil {
return nil
}
reader, _ := r.(*gzip.Reader) // Safe to ignore: pool return is best-effort
return reader
}
// PutGzipReader returns a gzip reader to the pool
func (m *Manager) PutGzipReader(r *gzip.Reader) {
if r == nil {
return
}
atomic.AddUint64(&m.stats.GzipPuts, 1)
_ = r.Reset(nil) // Safe to ignore: resetting to nil reader for pool reuse
m.gzipReaderPool.Put(r)
}
// GetStringBuilder returns a string builder from the pool
func (m *Manager) GetStringBuilder() *strings.Builder {
atomic.AddUint64(&m.stats.StringGets, 1)
sb, _ := m.stringBuilderPool.Get().(*strings.Builder) // Safe to ignore: pool return is best-effort
sb.Reset()
return sb
}
// PutStringBuilder returns a string builder to the pool
func (m *Manager) PutStringBuilder(sb *strings.Builder) {
if sb == nil {
return
}
atomic.AddUint64(&m.stats.StringPuts, 1)
// Reject oversized builders
if sb.Cap() > 16384 {
atomic.AddUint64(&m.stats.OversizedRejects, 1)
return
}
sb.Reset()
m.stringBuilderPool.Put(sb)
}
// GetJWTBuffer returns JWT parsing buffers from the pool
func (m *Manager) GetJWTBuffer() *JWTBuffer {
atomic.AddUint64(&m.stats.JWTGets, 1)
buf, _ := m.jwtBufferPool.Get().(*JWTBuffer) // Safe to ignore: pool return is best-effort
return buf
}
// PutJWTBuffer returns JWT parsing buffers to the pool
func (m *Manager) PutJWTBuffer(buf *JWTBuffer) {
if buf == nil {
return
}
atomic.AddUint64(&m.stats.JWTPuts, 1)
// Check for oversized buffers
if cap(buf.Header) > 2048 || cap(buf.Payload) > 8192 || cap(buf.Signature) > 2048 {
atomic.AddUint64(&m.stats.OversizedRejects, 1)
return
}
// Reset slices to zero length
buf.Header = buf.Header[:0]
buf.Payload = buf.Payload[:0]
buf.Signature = buf.Signature[:0]
m.jwtBufferPool.Put(buf)
}
// GetHTTPResponseBuffer returns an HTTP response buffer from the pool
func (m *Manager) GetHTTPResponseBuffer() []byte {
atomic.AddUint64(&m.stats.HTTPGets, 1)
buf, _ := m.httpResponsePool.Get().(*[]byte) // Safe to ignore: pool return is best-effort
return *buf
}
// PutHTTPResponseBuffer returns an HTTP response buffer to the pool
func (m *Manager) PutHTTPResponseBuffer(buf []byte) {
if buf == nil {
return
}
atomic.AddUint64(&m.stats.HTTPPuts, 1)
// Reject oversized buffers
if cap(buf) > 32768 {
atomic.AddUint64(&m.stats.OversizedRejects, 1)
return
}
buf = buf[:0]
m.httpResponsePool.Put(&buf)
}
// GetByteSlice returns a byte slice of the specified size from the pool
func (m *Manager) GetByteSlice(size int) []byte {
m.poolMu.RLock()
pool, exists := m.byteSlicePools[size]
m.poolMu.RUnlock()
if !exists {
// Round up to nearest power of 2
poolSize := 1
for poolSize < size {
poolSize *= 2
}
m.poolMu.Lock()
// Double-check after acquiring write lock
pool, exists = m.byteSlicePools[poolSize]
if !exists {
pool = &sync.Pool{
New: func() interface{} {
b := make([]byte, poolSize)
return &b
},
}
m.byteSlicePools[poolSize] = pool
}
m.poolMu.Unlock()
}
b, _ := pool.Get().(*[]byte) // Safe to ignore: pool return is best-effort
return (*b)[:size]
}
// PutByteSlice returns a byte slice to the pool
func (m *Manager) PutByteSlice(b []byte) {
if b == nil || cap(b) > 65536 { // Don't pool very large slices
return
}
size := cap(b)
m.poolMu.RLock()
pool, exists := m.byteSlicePools[size]
m.poolMu.RUnlock()
if exists {
b = b[:0]
pool.Put(&b)
}
}
// GetJSONEncoder returns a JSON encoder from the pool configured for the given writer
func (m *Manager) GetJSONEncoder(w io.Writer) *json.Encoder {
atomic.AddUint64(&m.stats.JSONEncoderGets, 1)
// Since json.Encoder doesn't support resetting, we create new ones each time
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false) // Disable HTML escaping for performance
return encoder
}
// PutJSONEncoder returns a JSON encoder to the pool
func (m *Manager) PutJSONEncoder(encoder *json.Encoder) {
if encoder == nil {
return
}
atomic.AddUint64(&m.stats.JSONEncoderPuts, 1)
// JSON encoders can't be reset, so we don't pool them
}
// GetJSONDecoder returns a JSON decoder from the pool configured for the given reader
func (m *Manager) GetJSONDecoder(r io.Reader) *json.Decoder {
atomic.AddUint64(&m.stats.JSONDecoderGets, 1)
// Since json.Decoder doesn't support resetting, we create new ones each time
return json.NewDecoder(r)
}
// PutJSONDecoder returns a JSON decoder to the pool
func (m *Manager) PutJSONDecoder(decoder *json.Decoder) {
if decoder == nil {
return
}
atomic.AddUint64(&m.stats.JSONDecoderPuts, 1)
// JSON decoders can't be reset, so we don't pool them
}
// GetStats returns current pool statistics
func (m *Manager) GetStats() PoolStats {
return PoolStats{
BufferGets: atomic.LoadUint64(&m.stats.BufferGets),
BufferPuts: atomic.LoadUint64(&m.stats.BufferPuts),
GzipGets: atomic.LoadUint64(&m.stats.GzipGets),
GzipPuts: atomic.LoadUint64(&m.stats.GzipPuts),
StringGets: atomic.LoadUint64(&m.stats.StringGets),
StringPuts: atomic.LoadUint64(&m.stats.StringPuts),
JWTGets: atomic.LoadUint64(&m.stats.JWTGets),
JWTPuts: atomic.LoadUint64(&m.stats.JWTPuts),
HTTPGets: atomic.LoadUint64(&m.stats.HTTPGets),
HTTPPuts: atomic.LoadUint64(&m.stats.HTTPPuts),
JSONEncoderGets: atomic.LoadUint64(&m.stats.JSONEncoderGets),
JSONEncoderPuts: atomic.LoadUint64(&m.stats.JSONEncoderPuts),
JSONDecoderGets: atomic.LoadUint64(&m.stats.JSONDecoderGets),
JSONDecoderPuts: atomic.LoadUint64(&m.stats.JSONDecoderPuts),
OversizedRejects: atomic.LoadUint64(&m.stats.OversizedRejects),
}
}
// ResetStats resets all statistics counters
func (m *Manager) ResetStats() {
atomic.StoreUint64(&m.stats.BufferGets, 0)
atomic.StoreUint64(&m.stats.BufferPuts, 0)
atomic.StoreUint64(&m.stats.GzipGets, 0)
atomic.StoreUint64(&m.stats.GzipPuts, 0)
atomic.StoreUint64(&m.stats.StringGets, 0)
atomic.StoreUint64(&m.stats.StringPuts, 0)
atomic.StoreUint64(&m.stats.JWTGets, 0)
atomic.StoreUint64(&m.stats.JWTPuts, 0)
atomic.StoreUint64(&m.stats.HTTPGets, 0)
atomic.StoreUint64(&m.stats.HTTPPuts, 0)
atomic.StoreUint64(&m.stats.JSONEncoderGets, 0)
atomic.StoreUint64(&m.stats.JSONEncoderPuts, 0)
atomic.StoreUint64(&m.stats.JSONDecoderGets, 0)
atomic.StoreUint64(&m.stats.JSONDecoderPuts, 0)
atomic.StoreUint64(&m.stats.OversizedRejects, 0)
}
// Global convenience functions
// Buffer returns a buffer from the global pool
func Buffer(sizeHint int) *bytes.Buffer {
return Get().GetBuffer(sizeHint)
}
// ReturnBuffer returns a buffer to the global pool
func ReturnBuffer(buf *bytes.Buffer) {
Get().PutBuffer(buf)
}
// GzipWriter returns a gzip writer from the global pool
func GzipWriter() *gzip.Writer {
return Get().GetGzipWriter()
}
// ReturnGzipWriter returns a gzip writer to the global pool
func ReturnGzipWriter(w *gzip.Writer) {
Get().PutGzipWriter(w)
}
// StringBuilder returns a string builder from the global pool
func StringBuilder() *strings.Builder {
return Get().GetStringBuilder()
}
// ReturnStringBuilder returns a string builder to the global pool
func ReturnStringBuilder(sb *strings.Builder) {
Get().PutStringBuilder(sb)
}
// JWTBuffers returns JWT parsing buffers from the global pool
func JWTBuffers() *JWTBuffer {
return Get().GetJWTBuffer()
}
// ReturnJWTBuffers returns JWT parsing buffers to the global pool
func ReturnJWTBuffers(buf *JWTBuffer) {
Get().PutJWTBuffer(buf)
}
// HTTPBuffer returns an HTTP response buffer from the global pool
func HTTPBuffer() []byte {
return Get().GetHTTPResponseBuffer()
}
// ReturnHTTPBuffer returns an HTTP response buffer to the global pool
func ReturnHTTPBuffer(buf []byte) {
Get().PutHTTPResponseBuffer(buf)
}
// ByteSlice returns a byte slice from the global pool
func ByteSlice(size int) []byte {
return Get().GetByteSlice(size)
}
// ReturnByteSlice returns a byte slice to the global pool
func ReturnByteSlice(b []byte) {
Get().PutByteSlice(b)
}
// JSONEncoder returns a JSON encoder from the global pool
func JSONEncoder(w io.Writer) *json.Encoder {
return Get().GetJSONEncoder(w)
}
// ReturnJSONEncoder returns a JSON encoder to the global pool
func ReturnJSONEncoder(encoder *json.Encoder) {
Get().PutJSONEncoder(encoder)
}
// JSONDecoder returns a JSON decoder from the global pool
func JSONDecoder(r io.Reader) *json.Decoder {
return Get().GetJSONDecoder(r)
}
// ReturnJSONDecoder returns a JSON decoder to the global pool
func ReturnJSONDecoder(decoder *json.Decoder) {
Get().PutJSONDecoder(decoder)
}