Skip to content

Conversation

@gaby
Copy link
Member

@gaby gaby commented Dec 25, 2025

Summary

  • introduce a pooled buffer for static path sanitization to reuse memory when normalizing backslashes
  • keep sanitized paths safe from pooled storage while normalizing and returning cleaned values
  • add a regression test and benchmark for repeated backslash static requests to validate behavior and allocations

Related #3767

Copilot AI review requested due to automatic review settings December 25, 2025 18:39
@gaby gaby requested a review from a team as a code owner December 25, 2025 18:39
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds pooled buffer handling to static middleware sanitization: uses a sync.Pool for temporary buffers, normalizes backslashes to slashes, repeatedly URL-unescapes, rejects NUL bytes, detaches pooled storage to a string before returning, and adds tests + a benchmark for backslash paths.

Changes

Cohort / File(s) Summary
Static middleware core
middleware/static/static.go
Adds sanitizeBufPool and putSanitizeBuf; moves ErrInvalidPath into a grouped var block; uses pooled buffers when backslashes present; replaces \ with /; repeatedly calls url.PathUnescape until stable; rejects NUL (0x00); converts pooled buffer to string before return and returns/reset buffers to pool.
Tests & benchmark
middleware/static/static_test.go
Adds Test_Static_BackslashRequests and Benchmark_Static_BackslashRequests to validate/measure handling of request paths containing backslashes; adds an extra route to exercise Next/skip behavior in tests.

Sequence Diagram(s)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐇 I hop on pooled buffers, nimble and bright,
I swap backslashes for slashes in flight.
I unescape the paths, banish NUL from sight,
I hand back my tray tidy — then nibble a bite. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description is concise but vague about implementation details and omits required template sections like Changes introduced checklist and Type of change. Expand the description to include checked items from the template (e.g., performance benchmarks, type of change), and provide more detail about the implementation and benefits.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing sync.Pool for static path sanitization, which is the primary focus of the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-sync.pool-for-buffer-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gaby, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the performance and memory efficiency of the static file serving middleware by implementing a buffer pooling mechanism for path sanitization. The change specifically targets the handling of paths containing backslashes, ensuring that the conversion to forward slashes is done with reduced memory allocations, thereby improving overall resource utilization.

Highlights

  • Memory Optimization: Introduced a sync.Pool for []byte buffers within the sanitizePath function to reuse memory when normalizing backslashes in static file paths, significantly reducing allocations.
  • Path Sanitization: The sanitizePath function now utilizes a pooled buffer to efficiently convert backslashes to forward slashes, ensuring sanitized paths are returned safely while minimizing memory churn.
  • Testing and Benchmarking: Added a new regression test (Test_Static_BackslashRequests) and a benchmark (Benchmark_Static_BackslashRequests) to validate the correct behavior and measure the allocation improvements for static requests containing backslashes.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ReneWerner87 ReneWerner87 added this to v3 Dec 25, 2025
@ReneWerner87 ReneWerner87 added this to the v3 milestone Dec 25, 2025
@gaby gaby changed the title 🧹 chore: reuse buffers in static path sanitization 🧹 chore: Use sync.Pool for static path sanitization Dec 25, 2025
@codecov
Copy link

codecov bot commented Dec 25, 2025

Codecov Report

❌ Patch coverage is 76.66667% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.55%. Comparing base (93830f2) to head (a9e16b2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
middleware/static/static.go 76.66% 4 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3972      +/-   ##
==========================================
- Coverage   91.59%   91.55%   -0.04%     
==========================================
  Files         119      119              
  Lines       10194    10213      +19     
==========================================
+ Hits         9337     9351      +14     
- Misses        543      546       +3     
- Partials      314      316       +2     
Flag Coverage Δ
unittests 91.55% <76.66%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a sync.Pool to reuse buffers for path sanitization, which is a good optimization to reduce memory allocations. My review identifies a couple of issues in the implementation. The pool's New function is configured with an incorrect return type, which prevents the optimization from working as intended. Additionally, there is no limit on the size of buffers returned to the pool, creating a risk of memory bloat. I've also included a suggestion to improve the new benchmark test for better accuracy.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces buffer pooling for static path sanitization to improve performance by reusing memory when normalizing backslashes in file paths. The implementation uses a sync.Pool to reduce allocations during path processing.

Key Changes:

  • Adds a sync.Pool for byte buffer reuse in backslash normalization
  • Replaces direct buffer allocation with pooled buffer acquisition
  • Includes regression test and benchmark for backslash path handling

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
middleware/static/static.go Introduces sync.Pool for buffer reuse in sanitizePath function with pooled buffer acquisition and management
middleware/static/static_test.go Adds regression test Test_Static_BackslashRequests and benchmark Benchmark_Static_BackslashRequests to validate backslash path handling

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
middleware/static/static.go (1)

44-53: Consider simplifying the nil check.

After a successful type assertion to *[]byte, item cannot be nil (the assertion would have failed). The check item == nil on line 45 is redundant. However, this is a minor clarity issue.

🔎 Proposed simplification
-		item, ok := sanitizeBufPool.Get().(*[]byte)
-		if !ok || item == nil {
-			item = new([]byte)
-		}
-		bufPtr = item
+		bufPtr, _ = sanitizeBufPool.Get().(*[]byte)
+		if bufPtr == nil {
+			bufPtr = new([]byte)
+		}
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93830f2 and 24fb6da.

📒 Files selected for processing (2)
  • middleware/static/static.go
  • middleware/static/static_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

When adding Go tests, always invoke t.Parallel() at the start of each test and subtest to maximize concurrency

Files:

  • middleware/static/static_test.go
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • middleware/static/static_test.go
  • middleware/static/static.go
🧠 Learnings (1)
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • middleware/static/static_test.go
🧬 Code graph analysis (1)
middleware/static/static_test.go (2)
middleware/static/static.go (1)
  • New (108-254)
constants.go (1)
  • MIMETextCSSCharsetUTF8 (37-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Agent
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: Compare
  • GitHub Check: repeated
  • GitHub Check: lint
🔇 Additional comments (3)
middleware/static/static.go (1)

55-65: LGTM!

The backslash replacement, string conversion (which safely copies before pool return), and buffer reset logic are correctly implemented. The else branch appropriately maintains the existing fast path using utils.UnsafeString when no backslashes are present.

middleware/static/static_test.go (2)

399-416: LGTM!

The test correctly invokes t.Parallel() per coding guidelines. The 3-iteration loop is a sensible regression test to validate that pooled buffer reuse doesn't corrupt subsequent requests. Assertions cover status, content type, and body content appropriately.


1145-1162: LGTM!

The benchmark is well-structured with b.ReportAllocs() and b.ResetTimer() correctly positioned. Using b.Loop() aligns with modern Go benchmarking patterns. Properly closing the response body prevents resource leaks during benchmark execution.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
middleware/static/static.go (2)

97-100: Consider adding a size limit when returning buffers to the pool.

The code returns buffers of any size to the pool (line 99), which can cause memory bloat if very long paths are processed. Large buffers remain in the pool indefinitely since sync.Pool doesn't shrink or impose size limits.

🔎 Proposed fix
 	if buf != nil {
 		s = string([]byte(s))        // detach from pooled storage before reuse
-		sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+		if cap(buf) <= 4096 { // Avoid pooling overly large buffers
+			sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+		}
 	}

98-98: The detach operation may be unnecessary.

At line 98, s has already been processed by pathpkg.Clean("/" + s) at line 80, which creates a new string through concatenation. This means s is already detached from buf's underlying storage, making the explicit detach string([]byte(s)) redundant and causing an unnecessary allocation.

🔎 Proposed simplification

You could safely remove line 98 since s is guaranteed to be detached after line 80's string concatenation:

 	if buf != nil {
-		s = string([]byte(s))        // detach from pooled storage before reuse
 		sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
 	}

However, keeping it provides defensive safety at the cost of a small allocation, so this is optional.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03dd00a and 3a8f46a.

📒 Files selected for processing (1)
  • middleware/static/static.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • middleware/static/static.go
🧠 Learnings (2)
📚 Learning: 2024-10-02T23:02:12.306Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/session.go:46-61
Timestamp: 2024-10-02T23:02:12.306Z
Learning: In this codebase, the `sessionPool` only contains `Session` instances, so type assertions without additional checks are acceptable.

Applied to files:

  • middleware/static/static.go
📚 Learning: 2025-09-28T17:39:26.644Z
Learnt from: arturmelanchyk
Repo: gofiber/fiber PR: 3768
File: client/request.go:162-166
Timestamp: 2025-09-28T17:39:26.644Z
Learning: In fasthttp-based code like gofiber, using utils.UnsafeString(key) instead of string(key) is acceptable when values are used immediately within the same call stack and not stored for later use, as it avoids allocations and aligns with fasthttp's zero-allocation design philosophy.

Applied to files:

  • middleware/static/static.go
🧬 Code graph analysis (1)
middleware/static/static.go (1)
app.go (1)
  • New (522-635)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: repeated
  • GitHub Check: Compare
🔇 Additional comments (2)
middleware/static/static.go (2)

22-30: LGTM: Pool type is now consistent.

The pool correctly returns []byte (line 27) which matches the type assertion at line 44. The initial capacity of 64 bytes is reasonable for typical static file paths.


51-58: LGTM: Backslash sanitization logic is correct.

The buffer copy and in-place backslash-to-forward-slash replacement is efficient. Using utils.UnsafeString at line 58 aligns with fasthttp's zero-allocation philosophy and is safe here since the string is immediately processed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
middleware/static/static.go (2)

70-85: Resource leak: pooled buffer not returned on early error returns.

When the path contains backslashes, buf is obtained from the pool. If the unescape fails (line 74) or a null byte is detected (line 84), the function returns early without returning buf to the pool, causing buffer leakage.

🔎 Proposed fix
 	// repeatedly unescape until it no longer changes, catching errors
 	for strings.IndexByte(s, '%') >= 0 {
 		us, err := url.PathUnescape(s)
 		if err != nil {
+			if buf != nil {
+				sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+			}
 			return nil, ErrInvalidPath
 		}
 		if us == s {
 			break
 		}
 		s = us
 	}

 	// reject any null bytes
 	if strings.IndexByte(s, '\x00') >= 0 {
+		if buf != nil {
+			sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+		}
 		return nil, ErrInvalidPath
 	}

89-98: Resource leak: pooled buffer not returned on filesystem validation returns.

Similar to the previous issue, early returns at lines 92 and 95 do not return buf to the pool when a path with backslashes triggers pool usage.

🔎 Proposed fix
 	if filesystem != nil {
 		s = utils.TrimLeft(s, '/')
 		if s == "" {
+			if buf != nil {
+				sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+			}
 			return []byte("/"), nil
 		}
 		if !fs.ValidPath(s) {
+			if buf != nil {
+				sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+			}
 			return nil, ErrInvalidPath
 		}
 		s = "/" + s
 	}

Consider extracting the buffer return logic to a helper function or using defer with a cleanup function to reduce repetition across all return paths.

🧹 Nitpick comments (1)
middleware/static/static.go (1)

104-107: Add size limit to prevent memory bloat from large buffers.

The pool returns buffers of any size. A request with a very long path allocates a large buffer that gets stored in the pool indefinitely. Since sync.Pool doesn't shrink, this can lead to memory bloat over time.

🔎 Proposed fix with size limit
 	if buf != nil {
 		s = string([]byte(s))        // detach from pooled storage before reuse
-		sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+		if cap(buf) <= 4096 { // Avoid pooling overly large buffers
+			sanitizeBufPool.Put(buf[:0]) //nolint:staticcheck // []byte reuse is intentional
+		}
 	}

Note: The detachment string([]byte(s)) on line 105 is defensive but likely redundant since s is already a new allocation after pathpkg.Clean("/" + s) on line 87. However, keeping it as a safety measure is reasonable.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a8f46a and c01b54a.

📒 Files selected for processing (1)
  • middleware/static/static.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • middleware/static/static.go
🧠 Learnings (2)
📚 Learning: 2024-10-02T23:02:12.306Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/session.go:46-61
Timestamp: 2024-10-02T23:02:12.306Z
Learning: In this codebase, the `sessionPool` only contains `Session` instances, so type assertions without additional checks are acceptable.

Applied to files:

  • middleware/static/static.go
📚 Learning: 2025-09-28T17:39:26.644Z
Learnt from: arturmelanchyk
Repo: gofiber/fiber PR: 3768
File: client/request.go:162-166
Timestamp: 2025-09-28T17:39:26.644Z
Learning: In fasthttp-based code like gofiber, using utils.UnsafeString(key) instead of string(key) is acceptable when values are used immediately within the same call stack and not stored for later use, as it avoids allocations and aligns with fasthttp's zero-allocation design philosophy.

Applied to files:

  • middleware/static/static.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Compare
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: repeated
  • GitHub Check: lint
🔇 Additional comments (2)
middleware/static/static.go (2)

22-30: LGTM on pool type consistency.

The pool's New function now correctly returns []byte, matching the type assertion on line 45. The initial capacity of 64 bytes is reasonable for typical paths.


44-67: Buffer pooling logic is correctly implemented.

The code properly handles:

  • Type assertion matching the pool's return type
  • Returning the original pooled buffer before allocating a larger one (lines 52-55)
  • Tracking pooled vs newly-allocated buffers via pooledBuf

The use of utils.UnsafeString on line 65 is acceptable since the value is used immediately within the same call stack. Based on learnings, this aligns with fasthttp's zero-allocation design philosophy.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
middleware/static/static.go (1)

111-114: Defensive copy is likely unnecessary but acceptable.

Line 112 creates a safe copy via string([]byte(s)) before returning the buffer to the pool. However, by this point s has already been transformed by:

  • url.PathUnescape (line 79) - creates new string
  • pathpkg.Clean("/" + s) (line 94) - concatenation creates new string, Clean returns new string
  • Potential additional transformations (lines 97-109)

After line 94's concatenation, s should already be detached from the pooled buffer. The defensive copy trades a small allocation for guaranteed safety, which is reasonable for this code path.

If you want to make this more idiomatic and potentially more efficient, consider using strings.Clone(s) (available in Go 1.18+) instead of string([]byte(s)).

💡 Optional: Use strings.Clone for clearer intent

If the project uses Go 1.18+, you could replace line 112 with:

-		s = string([]byte(s)) // detach from pooled storage before reuse
+		s = strings.Clone(s) // detach from pooled storage before reuse

This more clearly expresses the intent and may be optimized by the compiler.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c01b54a and d14fe5b.

📒 Files selected for processing (1)
  • middleware/static/static.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • middleware/static/static.go
🧠 Learnings (2)
📚 Learning: 2024-10-02T23:02:12.306Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/session.go:46-61
Timestamp: 2024-10-02T23:02:12.306Z
Learning: In this codebase, the `sessionPool` only contains `Session` instances, so type assertions without additional checks are acceptable.

Applied to files:

  • middleware/static/static.go
📚 Learning: 2025-09-28T17:39:26.644Z
Learnt from: arturmelanchyk
Repo: gofiber/fiber PR: 3768
File: client/request.go:162-166
Timestamp: 2025-09-28T17:39:26.644Z
Learning: In fasthttp-based code like gofiber, using utils.UnsafeString(key) instead of string(key) is acceptable when values are used immediately within the same call stack and not stored for later use, as it avoids allocations and aligns with fasthttp's zero-allocation design philosophy.

Applied to files:

  • middleware/static/static.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Compare
  • GitHub Check: lint
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: repeated
🔇 Additional comments (4)
middleware/static/static.go (4)

22-30: LGTM! Pool configuration is type-consistent.

The sync.Pool correctly returns []byte values, which matches the type assertion at line 53. The initial 64-byte capacity is reasonable for typical path lengths.


34-38: LGTM! Proper buffer size limiting.

The 8KB threshold prevents large buffers from accumulating in the pool, which is good practice to avoid memory bloat. Resetting the buffer length to zero before reuse is correct.


52-64: LGTM! Proper buffer acquisition and capacity handling.

The logic correctly:

  • Retrieves a buffer from the pool with proper type assertion
  • Tracks the pooled buffer separately in pooledBuf
  • Returns the original pooled buffer before allocating a new one when capacity is insufficient (lines 60-61)

This properly addresses the resource leak concern from previous reviews.


65-75: LGTM! Efficient buffer usage with proper safety handling.

The utils.UnsafeString(buf) at line 72 efficiently avoids an allocation. The string s initially shares the buffer's underlying bytes, but:

  • The buffer is not modified after line 71
  • A safe copy is created at line 112 before the buffer is returned to the pool
  • String operations at lines 79, 94, etc. create new strings, detaching from the buffer

The safety strategy is sound.

@gaby gaby requested a review from Copilot December 25, 2025 23:41
@gaby
Copy link
Member Author

gaby commented Dec 25, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a sync.Pool to reduce memory allocations during static path sanitization, which is a good performance improvement. The changes in middleware/static/static.go correctly implement the pooling pattern, including detaching the string from the pooled buffer to prevent data races. I've made a few suggestions to simplify the buffer handling logic and remove a redundant check.

The new tests and benchmarks in middleware/static/static_test.go are valuable for verifying the changes. However, I found a critical issue in the new benchmark's loop syntax that will prevent it from compiling. Please see my detailed comments.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment on lines 31 to 72
if bytes.IndexByte(p, '\\') >= 0 {
b := make([]byte, len(p))
copy(b, p)
for i := range b {
if b[i] == '\\' {
b[i] = '/'
buf, ok = sanitizeBufPool.Get().([]byte)
if !ok {
buf = nil
} else {
pooledBuf = buf
}
if cap(buf) < len(p) {
if pooledBuf != nil {
putSanitizeBuf(pooledBuf)
}
buf = make([]byte, len(p))
}
buf = buf[:len(p)]
copy(buf, p)
for i := range buf {
if buf[i] == '\\' {
buf[i] = '/'
}
}
s = utils.UnsafeString(b)
s = utils.UnsafeString(buf)
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical design flaw: The pooled buffer is acquired at lines 53-64 but only returned to the pool at line 113. There are multiple error return paths between these lines (lines 81, 92, 99, 102 in the full function) that would leak the buffer. Additionally, using UnsafeString creates a string backed by the buffer's memory while the buffer could potentially be returned to the pool and reused, risking data corruption. The correct approach is to either: 1) use string(buf) instead of UnsafeString(buf) for a safe copy and immediately return the buffer to the pool, or 2) use defer to ensure the buffer is always returned to the pool on all code paths.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +113
s = string([]byte(s)) // detach from pooled storage before reuse
putSanitizeBuf(buf)
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detachment logic here is ineffective because by this point in the code, the string 's' has already been reallocated by earlier operations, specifically the string concatenation at line 94 (pathpkg.Clean "/" + s in the full function). This means 's' is no longer backed by the pooled buffer, making this detachment unnecessary. The detachment should either happen immediately after line 72 where UnsafeString is used, or the code should use string(buf) instead of UnsafeString(buf) for a safe copy from the start.

Copilot uses AI. Check for mistakes.
Comment on lines 53 to 64
buf, ok = sanitizeBufPool.Get().([]byte)
if !ok {
buf = nil
} else {
pooledBuf = buf
}
if cap(buf) < len(p) {
if pooledBuf != nil {
putSanitizeBuf(pooledBuf)
}
buf = make([]byte, len(p))
}
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type assertion handling is unnecessarily complex. If the type assertion fails, setting buf to nil and then immediately checking if cap(buf) < len(p) will panic because cap(nil) is valid but the code doesn't need this branch. Since the pool's New function always returns []byte, the type assertion should never fail in practice. Consider simplifying this by removing the type assertion check, or at minimum handle the failure case by allocating a new buffer directly instead of setting buf to nil.

Suggested change
buf, ok = sanitizeBufPool.Get().([]byte)
if !ok {
buf = nil
} else {
pooledBuf = buf
}
if cap(buf) < len(p) {
if pooledBuf != nil {
putSanitizeBuf(pooledBuf)
}
buf = make([]byte, len(p))
}
bufAny := sanitizeBufPool.Get()
buf, ok = bufAny.([]byte)
if !ok {
buf = make([]byte, len(p))
} else if cap(buf) < len(p) {
putSanitizeBuf(buf)
buf = make([]byte, len(p))
} else {
pooledBuf = buf
}

Copilot uses AI. Check for mistakes.
},
}
)

Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant value 8KB seems arbitrary and lacks justification. Consider adding a comment explaining why this specific size limit was chosen for pooled buffers, especially in the context of typical path lengths in web applications.

Suggested change
// sanitizeBufPoolMaxSize caps the size of buffers we keep in the pool.
// 8KB is a conservative upper bound for typical URL/path lengths in web applications:
// it comfortably covers normal request paths while avoiding retention of unusually large
// buffers, which could otherwise increase memory usage if pooled.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
middleware/static/static.go (1)

77-109: Critical: Pooled buffer leaked on early returns.

Lines 81, 92, 99, and 102 return early without releasing the pooled buffer acquired at line 53. High-traffic scenarios with invalid paths will leak buffers from the pool, increasing allocation pressure and defeating the pool's purpose.

🔎 Recommended fix using defer

Insert a deferred cleanup immediately after buffer acquisition:

 	if bytes.IndexByte(p, '\\') >= 0 {
 		buf, ok = sanitizeBufPool.Get().([]byte)
 		if !ok {
 			buf = nil
 		} else {
 			pooledBuf = buf
 		}
 		if cap(buf) < len(p) {
 			if pooledBuf != nil {
 				putSanitizeBuf(pooledBuf)
 			}
 			buf = make([]byte, len(p))
 		}
+		defer func() {
+			if buf != nil {
+				putSanitizeBuf(buf)
+			}
+		}()
 		buf = buf[:len(p)]
 		copy(buf, p)
 		for i := range buf {
 			if buf[i] == '\\' {
 				buf[i] = '/'
 			}
 		}
 		s = utils.UnsafeString(buf)
 	} else {
 		s = utils.UnsafeString(p)
 	}

Then remove the manual cleanup at lines 111-114:

-	if buf != nil {
-		s = string([]byte(s)) // detach from pooled storage before reuse
-		putSanitizeBuf(buf)
-	}
-
 	return utils.UnsafeBytes(s), nil
♻️ Duplicate comments (1)
middleware/static/static.go (1)

111-114: Unnecessary allocation: detachment is redundant.

Line 112's detachment string([]byte(s)) is unnecessary. By this point, s has been reallocated by PathUnescape (line 79) and pathpkg.Clean (line 94), so it no longer references the pooled buffer. This creates an extra copy on every backslash-containing request.

The detachment can be removed entirely:

 	if buf != nil {
-		s = string([]byte(s)) // detach from pooled storage before reuse
 		putSanitizeBuf(buf)
 	}

Note: This comment assumes the critical buffer leak issue is addressed. If using the defer-based fix suggested above, lines 111-114 would be removed entirely.

🧹 Nitpick comments (1)
middleware/static/static.go (1)

32-32: Consider documenting the buffer size limit.

The 8KB limit lacks justification. Adding a comment would help future maintainers understand the rationale.

📝 Suggested documentation
+// sanitizeBufPoolMaxSize caps pooled buffer size to prevent memory bloat.
+// 8KB covers typical URL/path lengths while avoiding retention of unusually large buffers.
 const sanitizeBufPoolMaxSize = 8 * 1024
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d14fe5b and a9e16b2.

📒 Files selected for processing (1)
  • middleware/static/static.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • middleware/static/static.go
🧠 Learnings (2)
📚 Learning: 2024-10-02T23:02:12.306Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/session.go:46-61
Timestamp: 2024-10-02T23:02:12.306Z
Learning: In this codebase, the `sessionPool` only contains `Session` instances, so type assertions without additional checks are acceptable.

Applied to files:

  • middleware/static/static.go
📚 Learning: 2025-09-28T17:39:26.644Z
Learnt from: arturmelanchyk
Repo: gofiber/fiber PR: 3768
File: client/request.go:162-166
Timestamp: 2025-09-28T17:39:26.644Z
Learning: In fasthttp-based code like gofiber, using utils.UnsafeString(key) instead of string(key) is acceptable when values are used immediately within the same call stack and not stored for later use, as it avoids allocations and aligns with fasthttp's zero-allocation design philosophy.

Applied to files:

  • middleware/static/static.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: lint
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: repeated
  • GitHub Check: Compare

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
middleware/static/static.go (1)

52-71: Critical: Resource leak on error paths.

The pooled buffer acquired at line 53 is only returned to the pool at line 109, but there are multiple early return statements that bypass this cleanup (lines 77, 87, 95, 98). Each error path leaks a buffer from the pool, eventually degrading performance.

🔎 Recommended fix using defer
 	if bytes.IndexByte(p, '\\') >= 0 {
 		buf, ok := sanitizeBufPool.Get().([]byte)
 		if !ok {
 			buf = make([]byte, 0, 64)
 		}
 		pooledBuf = buf
+		defer func() {
+			if buf != nil {
+				// Ensure string is detached from pooled storage
+				putSanitizeBuf(buf)
+			}
+		}()
 		if cap(buf) < len(p) {
 			if pooledBuf != nil {
 				putSanitizeBuf(pooledBuf)
 			}
 			buf = make([]byte, len(p))
 		}
 		buf = buf[:len(p)]
 		copy(buf, p)
 		for i := range buf {
 			if buf[i] == '\\' {
 				buf[i] = '/'
 			}
 		}
 		s = utils.UnsafeString(buf)
 	} else {
 		s = utils.UnsafeString(p)
 	}

Then remove the manual Put at lines 107-110 since defer handles it:

-	if buf != nil {
-		s = string([]byte(s)) // detach from pooled storage before reuse
-		putSanitizeBuf(buf)
-	}
-
 	return utils.UnsafeBytes(s), nil

Note: You'll also need to ensure s is detached from the buffer before the defer runs. Since the string operations (PathUnescape, Clean, concatenation) between lines 74-104 create new string allocations, s should already be detached by the time defer executes. However, to be absolutely safe, you could do s = strings.Clone(s) before the return if using Go 1.18+, or keep a simpler detachment in the defer itself.

🧹 Nitpick comments (2)
middleware/static/static.go (2)

32-32: Add documentation for the buffer size limit.

The 8KB limit is reasonable for typical web application paths, but consider adding a comment explaining this choice to improve code maintainability.

🔎 Suggested documentation
+// sanitizeBufPoolMaxSize caps the size of buffers retained in the pool.
+// 8KB accommodates typical URL/path lengths while preventing memory bloat
+// from unusually large paths.
 const sanitizeBufPoolMaxSize = 8 * 1024

107-110: Consider simplifying the string detachment.

The expression string([]byte(s)) at line 108 performs two conversions to detach the string from pooled storage. While it works, it's somewhat convoluted.

Note: This cleanup block is only reached on the success path. The resource leak on error paths (flagged separately) must be addressed first, likely using defer which would move this logic.

Alternative approaches

If using Go 1.18+:

s = strings.Clone(s)

Or more explicitly:

s = string(append([]byte(nil), s...))

However, in practice, the intermediate string operations (PathUnescape, Clean, concatenation) at lines 74-104 likely create new allocations anyway, making this detachment defensive rather than strictly necessary.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9e16b2 and f7e302e.

📒 Files selected for processing (1)
  • middleware/static/static.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • middleware/static/static.go
🧠 Learnings (2)
📚 Learning: 2025-09-28T17:39:26.644Z
Learnt from: arturmelanchyk
Repo: gofiber/fiber PR: 3768
File: client/request.go:162-166
Timestamp: 2025-09-28T17:39:26.644Z
Learning: In fasthttp-based code like gofiber, using utils.UnsafeString(key) instead of string(key) is acceptable when values are used immediately within the same call stack and not stored for later use, as it avoids allocations and aligns with fasthttp's zero-allocation design philosophy.

Applied to files:

  • middleware/static/static.go
📚 Learning: 2024-10-02T23:02:12.306Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/session.go:46-61
Timestamp: 2024-10-02T23:02:12.306Z
Learning: In this codebase, the `sessionPool` only contains `Session` instances, so type assertions without additional checks are acceptable.

Applied to files:

  • middleware/static/static.go
🪛 GitHub Actions: golangci-lint
middleware/static/static.go

[error] 46-46: golangci-lint: declared and not used: ok (typecheck)

🪛 GitHub Actions: Modernize Lint
middleware/static/static.go

[error] 46-46: Command 'go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test=false ./...' failed: declared and not used: ok

🪛 GitHub Actions: Run govulncheck
middleware/static/static.go

[error] 46-46: declared and not used: ok

🪛 GitHub Check: govulncheck-check
middleware/static/static.go

[failure] 46-46:
declared and not used: ok

🪛 GitHub Check: lint
middleware/static/static.go

[failure] 46-46:
declared and not used: ok (typecheck)

🪛 GitHub Check: modernize
middleware/static/static.go

[failure] 46-46:
declared and not used: ok

🪛 GitHub Check: unit (1.25.x, ubuntu-latest)
middleware/static/static.go

[failure] 46-46:
declared and not used: ok

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Compare
  • GitHub Check: repeated
🔇 Additional comments (2)
middleware/static/static.go (2)

22-30: LGTM! The buffer pool initialization is correct.

The sync.Pool correctly returns []byte value type with an initial capacity of 64 bytes, which matches the type assertion used later at line 53.


34-38: LGTM! The buffer return logic is correct.

The function properly checks buffer capacity before pooling and resets the length to 0, preventing both memory bloat and buffer content leakage.

Comment on lines +43 to +48
var (
buf []byte
pooledBuf []byte
ok bool
s string
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the unused variable causing build failures.

The variable ok is declared but never used in the type assertion at line 53, causing all pipeline checks to fail. The type assertion should check the boolean return value.

🔎 Proposed fix
 	var (
 		buf       []byte
 		pooledBuf []byte
-		ok        bool
 		s         string
 	)

And then at line 53, handle the assertion properly:

 	if bytes.IndexByte(p, '\\') >= 0 {
-		buf = sanitizeBufPool.Get().([]byte)
+		buf, ok := sanitizeBufPool.Get().([]byte)
+		if !ok {
+			buf = make([]byte, 0, 64)
+		}
 		pooledBuf = buf

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: golangci-lint

[error] 46-46: golangci-lint: declared and not used: ok (typecheck)

🪛 GitHub Actions: Modernize Lint

[error] 46-46: Command 'go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test=false ./...' failed: declared and not used: ok

🪛 GitHub Actions: Run govulncheck

[error] 46-46: declared and not used: ok

🪛 GitHub Check: govulncheck-check

[failure] 46-46:
declared and not used: ok

🪛 GitHub Check: lint

[failure] 46-46:
declared and not used: ok (typecheck)

🪛 GitHub Check: modernize

[failure] 46-46:
declared and not used: ok

🪛 GitHub Check: unit (1.25.x, ubuntu-latest)

[failure] 46-46:
declared and not used: ok

🤖 Prompt for AI Agents
In middleware/static/static.go around lines 43 to 48, the variable `ok` is
declared but never used which causes build failures; update the type assertion
at line 53 to capture the boolean return value (e.g., assign to a variable and
check it) and then branch on that boolean so `ok` is used (or remove the unused
`ok` declaration and replace the assertion with the proper two-value form and a
check), ensuring the code handles the failed assertion path appropriately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants