Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
e01d7af
Optimize cache vary parsing
gaby Dec 28, 2025
2e8643d
Fix cache s-maxage expiry timing
gaby Dec 28, 2025
9cc72f3
Refactor cache min-fresh handling
gaby Dec 28, 2025
36d80b6
Simplify vary parsing and shared cache checks
gaby Dec 28, 2025
b881bf6
Stabilize cache stale warning test
gaby Dec 28, 2025
cecea86
Stabilize cache stale warning test
gaby Dec 28, 2025
7eb5e6b
Simplify vary key hashing
gaby Dec 28, 2025
a278e5f
Fix errcheck noise and stabilize custom expiration test
gaby Dec 28, 2025
d215551
Fix cache header name in custom expiration test
gaby Dec 28, 2025
d950d26
Silence errcheck on vary hash writes
gaby Dec 28, 2025
7989a94
Fix errcheck assignment on vary hash writes
gaby Dec 28, 2025
1394681
Document errcheck suppressions on vary hash writes
gaby Dec 28, 2025
7a73c4e
Avoid mutating cached entry when computing age
gaby Dec 28, 2025
6ef236e
Remove redundant forceRevalidate check
gaby Dec 30, 2025
6446dea
refactor: optimize cache handling for authentication with buffer pooling
ReneWerner87 Jan 2, 2026
5abc2e8
Error: /home/runner/work/fiber/fiber/middleware/cache/cache.go:134:15…
ReneWerner87 Jan 2, 2026
a836fad
refactor: improve Cache-Control and Age header handling for compliance
ReneWerner87 Jan 2, 2026
3b4c22e
Merge branch 'main' into update-cache-handling-for-authentication
gaby Jan 3, 2026
5ca7a08
fix lint issues
gaby Jan 3, 2026
3b39270
fix linter
gaby Jan 3, 2026
d8b3408
reset cencoding during release
gaby Jan 3, 2026
d76b4bf
Refine cache control parsing with utils.EqualFold
gaby Jan 3, 2026
27505d2
Simplify error paths while keeping heap protection
gaby Jan 3, 2026
c5ad490
Fix proxy header benchmarks
gaby Jan 3, 2026
4b8608c
Fix proxy header benchmarks
gaby Jan 3, 2026
80d999e
Merge pull request #3979 from gofiber/improve-cache-middleware-perfor…
gaby Jan 3, 2026
ce8d502
Initial plan
Copilot Jan 4, 2026
876d3c5
Fix heap entry cleanup during cache revalidation
Copilot Jan 4, 2026
07e46b9
Fix data race in cache entry date mutation
Copilot Jan 4, 2026
8316bb6
Fix heap accounting during revalidation
Copilot Jan 4, 2026
be74d15
Fix heap entry removal timing during revalidation
Copilot Jan 4, 2026
7951c84
Add comprehensive tests for revalidation with MaxBytes
Copilot Jan 4, 2026
4dc91ca
Run make generate and make format
Copilot Jan 4, 2026
33261cb
Fix errcheck linter warnings in test file
Copilot Jan 4, 2026
1e57098
Merge pull request #3985 from gofiber/copilot/sub-pr-3973
gaby Jan 4, 2026
8ae61dc
Initial plan
Copilot Jan 4, 2026
14cb87e
Implement RFC 9111 compliant quoted-string parsing for Cache-Control …
Copilot Jan 4, 2026
8862fae
Merge pull request #3986 from gofiber/copilot/sub-pr-3973
gaby Jan 4, 2026
ed1884b
Initial plan
Copilot Jan 4, 2026
47db627
Fix race condition and inconsistent state in cache eviction
Copilot Jan 4, 2026
6c51896
Address code review comments: improve comments and add test coverage
Copilot Jan 4, 2026
36534b7
Increase cache middleware test coverage with additional MaxBytes tests
Copilot Jan 4, 2026
04e0ac6
Add comprehensive helper function tests to improve coverage
Copilot Jan 5, 2026
9d57654
Add comprehensive cache control combination tests
Copilot Jan 5, 2026
4d0dfb1
Add extensive edge case tests to improve cache middleware coverage to…
Copilot Jan 5, 2026
9d1d928
Rename test functions and fix lint issues
Copilot Jan 5, 2026
d38fd70
Apply code review feedback: use context.Background() and remove line …
Copilot Jan 5, 2026
f3d04a2
Address review feedback: add TODO comment, improve test assertions, a…
Copilot Jan 5, 2026
7f97675
Fix lint issues in concurrency tests
Copilot Jan 5, 2026
6a8b0b5
Fix govet waitgroup errors: move wg.Add(1) before goroutine launch
Copilot Jan 5, 2026
94e8006
Suppress revive use-waitgroup-go warning with nolint comments
Copilot Jan 5, 2026
848d21f
Apply review feedback: enhance TODO comment and fix race condition in…
Copilot Jan 5, 2026
c4c425d
🐛 Fix eviction restoration for cache deletion failures
gaby Jan 5, 2026
f44d29f
Address code review feedback: fix synchronization and clarify comments
Copilot Jan 5, 2026
cd055eb
Run betteralign: optimize failingCacheStorage struct field order
Copilot Jan 5, 2026
4b48b8d
Update middleware/cache/cache_test.go
gaby Jan 5, 2026
21fbb78
Update middleware/cache/cache_test.go
gaby Jan 5, 2026
096269e
Fix refreshHeapIndex for in-memory storage and test race condition
Copilot Jan 5, 2026
b2667b8
Merge pull request #3987 from gofiber/copilot/sub-pr-3973
gaby Jan 5, 2026
21536d4
Merge branch 'main' into update-cache-handling-for-authentication
gaby Jan 5, 2026
de98e43
Remove unused mutatingStorage type
gaby Jan 5, 2026
5c044fc
Update middleware/cache/cache.go
gaby Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 114 additions & 11 deletions middleware/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)

type evictionCandidate struct {
key string
size uint
exp uint64
heapIdx int
}

redactKeys := !cfg.DisableValueRedaction

maskKey := func(key string) string {
Expand Down Expand Up @@ -192,6 +199,26 @@ func New(config ...Config) fiber.Handler {
storedBytes -= size
}

refreshHeapIndex := func(ctx context.Context, candidate evictionCandidate) error {
entry, err := manager.get(ctx, candidate.key)
if err != nil {
if errors.Is(err, errCacheMiss) {
return nil
}
return fmt.Errorf("cache: failed to reload key %q after eviction failure: %w", maskKey(candidate.key), err)
}

entry.heapidx = candidate.heapIdx

remainingTTL := max(time.Until(secondsToTime(entry.exp)), 0)

if err := manager.set(ctx, candidate.key, entry, remainingTTL); err != nil {
return fmt.Errorf("cache: failed to restore heap index for key %q: %w", maskKey(candidate.key), err)
}

return nil
}

// Return new handler
return func(c fiber.Ctx) error {
hasAuthorization := len(c.Request().Header.Peek(fiber.HeaderAuthorization)) > 0
Expand Down Expand Up @@ -581,20 +608,94 @@ func New(config ...Config) fiber.Handler {
return nil
}

// Remove oldest to make room for new without holding the lock during storage I/O.
if cfg.MaxBytes > 0 {
for {
// Eviction loop: atomically reserve space for new entry and evict old entries.
// Strategy:
// 1. Under lock: reserve space by pre-incrementing storedBytes, then collect entries to evict
// 2. Outside lock: perform I/O deletions
// 3. On deletion failure: restore storedBytes and return error
// 4. Track reservation with a flag; unreserve on early return via defer
var spaceReserved bool
defer func() {
// If we reserved space but the entry was not successfully added to heap, unreserve it
if cfg.MaxBytes > 0 && spaceReserved {
mux.Lock()
if storedBytes+bodySize <= cfg.MaxBytes {
storedBytes -= bodySize
mux.Unlock()
}
}()

if cfg.MaxBytes > 0 {
mux.Lock()
// Reserve space for the new entry first
storedBytes += bodySize
spaceReserved = true

// Now evict entries until we're under the limit
var keysToRemove []string
var sizesToRemove []uint
var candidates []evictionCandidate

for storedBytes > cfg.MaxBytes {
if heap.Len() == 0 {
// Can't evict more, unreserve space and fail
storedBytes -= bodySize
// Set spaceReserved to false so the deferred cleanup does not unreserve again
spaceReserved = false
mux.Unlock()
break
return errors.New("cache: insufficient space and no entries to evict")
}
next := heap.entries[0]
keyToRemove, size := heap.removeFirst()
keysToRemove = append(keysToRemove, keyToRemove)
sizesToRemove = append(sizesToRemove, size)
candidates = append(candidates, evictionCandidate{
key: keyToRemove,
size: size,
exp: next.exp,
})
storedBytes -= size
mux.Unlock()
}
mux.Unlock()

// Perform deletions outside the lock
if len(keysToRemove) > 0 {
for i, keyToRemove := range keysToRemove {
delErr := deleteKey(reqCtx, keyToRemove)
if delErr == nil {
continue
}

if err := deleteKey(reqCtx, keyToRemove); err != nil {
return fmt.Errorf("cache: failed to delete key %q while evicting: %w", maskKey(keyToRemove), err)
// Deletion failed: restore storedBytes for failed deletions
mux.Lock()
// Restore sizes of entries we failed to delete
for j := i; j < len(sizesToRemove); j++ {
storedBytes += sizesToRemove[j]
}
// Unreserve space for the new entry
storedBytes -= bodySize
spaceReserved = false

// Re-add entries to the heap to keep expiration tracking consistent
var restored []evictionCandidate
for j := i; j < len(candidates); j++ {
candidate := candidates[j]
candidate.heapIdx = heap.put(candidate.key, candidate.exp, candidate.size)
restored = append(restored, candidate)
}
mux.Unlock()

var restoreErr error
for _, candidate := range restored {
if err := refreshHeapIndex(reqCtx, candidate); err != nil {
restoreErr = errors.Join(restoreErr, err)
}
}

if restoreErr != nil {
return errors.Join(fmt.Errorf("cache: failed to delete key %q while evicting: %w", maskKey(keyToRemove), delErr), restoreErr)
}

return fmt.Errorf("cache: failed to delete key %q while evicting: %w", maskKey(keyToRemove), delErr)
}
}
}
Expand Down Expand Up @@ -731,13 +832,15 @@ func New(config ...Config) fiber.Handler {
e.exp = ts + 1
}

// Store entry in heap
// Store entry in heap (space already reserved in eviction phase)
var heapIdx int
if cfg.MaxBytes > 0 {
mux.Lock()
heapIdx = heap.put(key, e.exp, bodySize)
e.heapidx = heapIdx
storedBytes += bodySize
// Note: storedBytes was incremented during reservation, and evictions
// have already been accounted for, so no additional increment is needed
spaceReserved = false // Clear flag to prevent defer from unreserving
mux.Unlock()
}

Expand Down Expand Up @@ -1085,7 +1188,7 @@ func isHeuristicFreshness(e *item, cfg *Config, entryAge uint64) bool {
}

cacheControl := utils.UnsafeString(e.cacheControl)
if hasDirective(cacheControl, "max-age") || hasDirective(cacheControl, "s-maxage") {
if parsedCC := parseResponseCacheControl(utils.UnsafeBytes(cacheControl)); parsedCC.maxAgeSet || parsedCC.sMaxAgeSet {
return false
}

Expand Down
Loading
Loading