Summary
Minor performance findings from a full architecture review. Individual impact is low, but collectively worth a cleanup pass.
1. Cache heap unbounded growth
File: middleware/cache/heap.go:22
entries []heapEntry has no hard cap when MaxBytes config is 0 (default). Risk: OOM in deployments that don't set MaxBytes.
2. CookieJar unbounded map
File: client/cookiejar.go:47
hostCookies map[string][]storedCookie has no size limit or eviction. Slow memory growth in long-lived clients.
3. Logger timestamp goroutine has no shutdown
File: middleware/logger/logger.go:43-48
The timestamp update goroutine runs in a for {} time.Sleep() loop with no context cancellation or done channel. Prevents clean test teardown and graceful shutdown.
Fix: Accept a context.Context or done channel, select alongside time.Sleep.
4. SSE heartbeat goroutine cleanup latency
File: middleware/sse/sse.go:208-221
Heartbeat goroutine uses time.Sleep instead of select on the done channel. Connection teardown blocks for up to HeartbeatInterval.
Fix: Replace time.Sleep with time.NewTimer + select on done channel.
5. Memory storage GC goroutine potential deadlock
File: internal/storage/memory/memory.go:151
Unbuffered done channel send. If GC goroutine has already exited after Close(), the send blocks forever.
Fix: Use a buffered channel (make(chan struct{}, 1)) or select with default.
6. BaseURL() allocation
File: ctx.go:113-118
First BaseURL() call per request allocates 32B (documented with TODO comment: "53.8 ns/op 32 B/op 1 allocs/op").
7. Proxy middleware uses RLock for client reads
File: middleware/proxy/proxy.go:122,184
RLock on every proxied request for global client variable. Use atomic.Value for lock-free reads.
8. Client fileBufPool retains 1MB buffers
File: client/hooks.go:21-26
Pool creates []byte slices of 1MB. Under load, retains up to GOMAXPROCS × 1MB.
9. Shared state defensive copy
File: shared_state.go:115,335
append([]byte(nil), data...) copies entire value on every read. Correct for safety but costly for large values. Consider documenting or adding a GetRef() method.
10. Route() fallback allocates empty slices
File: ctx.go:364-371
make([]Handler, 0) and make([]string, 0) on fallback path. Use package-level pre-allocated empty slices.
Identified during a full performance architecture review of the Fiber codebase.
Summary
Minor performance findings from a full architecture review. Individual impact is low, but collectively worth a cleanup pass.
1. Cache heap unbounded growth
File:
middleware/cache/heap.go:22entries []heapEntryhas no hard cap whenMaxBytesconfig is 0 (default). Risk: OOM in deployments that don't setMaxBytes.2. CookieJar unbounded map
File:
client/cookiejar.go:47hostCookies map[string][]storedCookiehas no size limit or eviction. Slow memory growth in long-lived clients.3. Logger timestamp goroutine has no shutdown
File:
middleware/logger/logger.go:43-48The timestamp update goroutine runs in a
for {} time.Sleep()loop with no context cancellation or done channel. Prevents clean test teardown and graceful shutdown.Fix: Accept a
context.Contextor done channel,selectalongsidetime.Sleep.4. SSE heartbeat goroutine cleanup latency
File:
middleware/sse/sse.go:208-221Heartbeat goroutine uses
time.Sleepinstead ofselecton the done channel. Connection teardown blocks for up toHeartbeatInterval.Fix: Replace
time.Sleepwithtime.NewTimer+selecton done channel.5. Memory storage GC goroutine potential deadlock
File:
internal/storage/memory/memory.go:151Unbuffered
donechannel send. If GC goroutine has already exited afterClose(), the send blocks forever.Fix: Use a buffered channel (
make(chan struct{}, 1)) orselectwith default.6.
BaseURL()allocationFile:
ctx.go:113-118First
BaseURL()call per request allocates 32B (documented with TODO comment: "53.8 ns/op 32 B/op 1 allocs/op").7. Proxy middleware uses RLock for client reads
File:
middleware/proxy/proxy.go:122,184RLockon every proxied request for global client variable. Useatomic.Valuefor lock-free reads.8. Client fileBufPool retains 1MB buffers
File:
client/hooks.go:21-26Pool creates
[]byteslices of 1MB. Under load, retains up toGOMAXPROCS × 1MB.9. Shared state defensive copy
File:
shared_state.go:115,335append([]byte(nil), data...)copies entire value on every read. Correct for safety but costly for large values. Consider documenting or adding aGetRef()method.10. Route() fallback allocates empty slices
File:
ctx.go:364-371make([]Handler, 0)andmake([]string, 0)on fallback path. Use package-level pre-allocated empty slices.Identified during a full performance architecture review of the Fiber codebase.