- Add sync.Once to prevent double close of stopCh in Stop()
- Add nil fallback guard in NewStickySession (defaults to RoundRobin)
- Add atomic.Bool to make Start() idempotent
- Add tests for double Stop() and nil fallback scenarios
- Fix Select to check if cookie is expired before routing
- Add TestStickySession_ExpiredCookie test
- Expired cookies now trigger fallback + new cookie set
- Encode cookie as base64(target_url + | + timestamp) per spec
- Use cookie value (not targetURL) for shard key and session map keys
- Add missing sticky.Start() calls in tests
- Fix time precision in cookie encode/decode tests
- Add atomic EWMA Stats field to Target
- Implement LeastTime balancer with header_time and last_byte metrics
- Support Select and SelectExcluding with zero-lock design
- Add ResponseTimeRecorder interface for proxy integration
- Zero-lock atomic EWMA implementation using fixed-point arithmetic
- Supports header_time and last_byte_time tracking
- Concurrent-safe with CAS retry loop
- Auto-detect VERSION from git tags with fallback
- Extract mkdir as order-only prerequisite to eliminate duplication
- Add PERF_GCFLAGS/PERF_ASMFLAGS to cross-platform builds and install
- Merge bench-regression into bench-check, unify file naming
- Fix bench scope and sampling consistency (internal/ only, -run=^$)
- Fix test-cover scope to avoid un-tagged integration/e2e code
- Fix deprecated go get -u ./... to go get -u
- Add clean-mod target, clean benchmark artifacts in clean
- Remove phantom build-prod/build-perf from help
- Split docker long line for readability
- Add .PHONY declarations for all targets
Add matcher.ReleaseMatchResult(result) in the base handler to prevent
sync.Pool object leak. Every Match() call acquires from pool but the
caller never returned objects, causing unbounded pool growth.
Replace manual PEM text scanning with pem.Decode(). Returns proper
DER-encoded bytes instead of raw PEM text, fixing potential TLS
handshake failures with certificate chains.
Remove unused findMarker and matchMarker helpers.
- Check(): single GeoIP LookupCountry call, result reused for both
deny and allow checks. Removed goto label for structured flow.
- getClientIP(): single trusted proxy CIDR scan gates both
X-Forwarded-For and X-Real-IP processing.
- Pre-build extSet map for O(1) extension lookup instead of linear scan
- Replace bytes.ToLower allocation in supportsEncoding with
utils.BytesContainsFold for case-insensitive encoding detection
Add typesBytes and typesWildcardPrefix fields to Middleware, built once
at construction. isCompressible now uses pre-converted byte slices
instead of allocating []byte(t) per comparison per request.
Accept []byte directly instead of string, allowing callers to pass
fasthttp's ctx.Path() without string conversion. Internally uses
bytes.HasPrefix instead of strings.HasPrefix in radix tree search.
Three optimizations in the proxy cache hot path:
- Pre-build cacheIgnoreSet map once at Proxy creation instead of
per-response. Eliminates map allocation + linear scan per cached
response.
- Compute cache key once per request via computeCacheKey() closure.
Previously buildCacheKeyHash was called up to 5 times per request;
now computed on first access and reused.
- Pool UpstreamTiming objects with sync.Pool. Eliminates one heap
allocation per proxied request.
Replace string(connection)/strings.EqualFold/strings.ToLower with
bytes.EqualFold and utils.BytesContainsFold. Removes 2-4 heap
allocations per proxied request.
Replaces fnv.New64a() with direct inline hash computation over
fasthttp's []byte slices, eliminating 1 allocation per cache key
computation and 1 []byte(":") allocation.
- errcheck: check type assertions from sync.Pool.Get() in
loadbalance/balancer.go and matcher/radix.go
- errcheck: check type assertion from list.Element.Value in
resolver/resolver.go evictLRULocked
- revive: add doc comment for exported ReleaseMatchResult function
SelectByKey and SelectExcludingByKey previously had a RLock→RUnlock→
rebuildCircle(Lock)→RLock pattern when the hash ring was empty. Under
cold-start concurrency, multiple goroutines could trigger simultaneous
rebuild attempts.
Add atomic.Bool 'rebuilt' flag with ensureRebuilt() check before any
RLock acquisition:
- Fast path: atomic load returns true → skip rebuild, proceed to RLock
- Cold start: first caller rebuilds and sets flag, subsequent callers
see the flag and skip rebuild
- Rebuild() explicitly resets the flag for explicit ring invalidation
Eliminates the RLock→Unlock→Lock→RLock transition entirely. The ring
is guaranteed ready before RLock is acquired.
filterHealthy() allocated 2 slices (available + backups) per call.
filterHealthyAndExclude() allocated 3 (adds excludeSet map).
IPHash allocated fnv.New64a() object per call.
All triggered on every request's LB selection.
Changes:
- Add filterContext struct holding reusable buffers, managed by sync.Pool
- Replace filterHealthy → filterInto (writes into pooled buffers)
- Replace filterHealthyAndExclude → filterIntoExcluding (pooled buffers)
- Add inline fnvHash64a() to avoid fnv.New64a() heap allocation
- Update all 6 balancer algorithms (RoundRobin, WeightedRoundRobin,
LeastConnections, IPHash, Random, ConsistentHash) to use pooled
filterContext via acquire/release pattern
- ConsistentHash.SelectExcludingByKey also uses pool for targetSet
- Remove buildExcludeSet (merged into filterIntoExcluding)
Result: allocs/op reduced from 2-3 to 0-1 on all LB Select paths.