Release 0.7.5 (#70)

* Resolve issue with opaque tokens not being parsed correctly

* Increase test coverage

* Further improvements to test coverage and code quality

* Add new providers.

* fixup! Add new providers.

* Cleanup.

* fixup! Cleanup.

* fixup! fixup! Cleanup.

* fixup! fixup! fixup! Cleanup.

* fixup! fixup! fixup! fixup! Cleanup.

* Memory management optimisation

24 bytes per Put < 256-4096 bytes per buffer allocation avoided (10-170x difference)

* Pooling cleanup.
This commit is contained in:
2025-10-01 12:13:10 +01:00
committed by GitHub
parent 3bbc6a1608
commit c3f23cb99b
93 changed files with 26767 additions and 4230 deletions
+68
View File
@@ -6,6 +6,8 @@ package pool
import (
"bytes"
"compress/gzip"
"encoding/json"
"io"
"strings"
"sync"
"sync/atomic"
@@ -54,6 +56,10 @@ type PoolStats struct {
JWTPuts uint64
HTTPGets uint64
HTTPPuts uint64
JSONEncoderGets uint64
JSONEncoderPuts uint64
JSONDecoderGets uint64
JSONDecoderPuts uint64
OversizedRejects uint64
}
@@ -378,6 +384,40 @@ func (m *Manager) PutByteSlice(b []byte) {
}
}
// 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{
@@ -391,6 +431,10 @@ func (m *Manager) GetStats() PoolStats {
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),
}
}
@@ -407,6 +451,10 @@ func (m *Manager) ResetStats() {
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)
}
@@ -471,3 +519,23 @@ func ByteSlice(size int) []byte {
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)
}
+70
View File
@@ -0,0 +1,70 @@
// Package pool provides centralized memory pool management utilities
package pool
import (
"strings"
)
// BuildSessionName efficiently builds session names using pooled string builders
func BuildSessionName(baseName string, index int) string {
sb := StringBuilder()
defer ReturnStringBuilder(sb)
sb.WriteString(baseName)
sb.WriteRune('_')
// Efficient int to string conversion
if index < 10 {
sb.WriteRune('0' + rune(index))
} else {
sb.WriteString(intToString(index))
}
return sb.String()
}
// BuildCacheKey efficiently builds cache keys using pooled string builders
func BuildCacheKey(parts ...string) string {
sb := StringBuilder()
defer ReturnStringBuilder(sb)
for i, part := range parts {
if i > 0 {
sb.WriteRune(':')
}
sb.WriteString(part)
}
return sb.String()
}
// FormatString efficiently formats a string using a pooled string builder
func FormatString(format func(*strings.Builder)) string {
sb := StringBuilder()
defer ReturnStringBuilder(sb)
format(sb)
return sb.String()
}
// intToString converts int to string without allocation (for small numbers)
func intToString(n int) string {
if n < 0 {
return "-" + intToString(-n)
}
if n < 10 {
return string(rune('0' + n))
}
if n < 100 {
return string(rune('0'+n/10)) + string(rune('0'+n%10))
}
// Fall back to standard conversion for larger numbers
buf := make([]byte, 0, 20)
for n > 0 {
buf = append(buf, byte('0'+n%10))
n /= 10
}
// Reverse the buffer
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
buf[i], buf[j] = buf[j], buf[i]
}
return string(buf)
}