Skip to content

🧹 chore: Use sync.Pool for Client hooks#3758

Merged
gaby merged 8 commits intogofiber:mainfrom
arturmelanchyk:file-buffer-pool
Sep 26, 2025
Merged

🧹 chore: Use sync.Pool for Client hooks#3758
gaby merged 8 commits intogofiber:mainfrom
arturmelanchyk:file-buffer-pool

Conversation

@arturmelanchyk
Copy link
Contributor

@arturmelanchyk arturmelanchyk commented Sep 22, 2025

Description

This PR improves performance by introducing sync.Pool for fileBuf in parserRequestBodyFile function

$ benchstat old.txt sync-pool.txt
goos: darwin
goarch: arm64
pkg: github.com/gofiber/fiber/v3/client
cpu: Apple M3 Max
                          │   old.txt    │            sync-pool.txt            │
                          │    sec/op    │   sec/op     vs base                │
_Parser_Request_Body_File   24.168µ ± 2%   6.661µ ± 1%  -72.44% (p=0.000 n=20)

                          │   old.txt    │             sync-pool.txt              │
                          │     B/s      │      B/s       vs base                 │
_Parser_Request_Body_File   3.788Gi ± 2%   13.745Gi ± 1%  +262.83% (p=0.000 n=20)

                          │     old.txt     │            sync-pool.txt             │
                          │      B/op       │     B/op      vs base                │
_Parser_Request_Body_File   1028.138Ki ± 0%   4.095Ki ± 0%  -99.60% (p=0.000 n=20)

                          │  old.txt   │           sync-pool.txt           │
                          │ allocs/op  │ allocs/op   vs base               │
_Parser_Request_Body_File   112.0 ± 0%   111.0 ± 0%  -0.89% (p=0.000 n=20)

Changes introduced

List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.

  • Benchmarks: Describe any performance benchmarks and improvements related to the changes.
  • Documentation Update: Detail the updates made to the documentation and links to the changed files.
  • Changelog/What's New: Include a summary of the additions for the upcoming release notes.
  • Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
  • API Alignment with Express: Explain how the changes align with the Express API.
  • API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
  • Examples: Provide examples demonstrating the new features or changes in action.

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • Enhancement (improvement to existing features and functionality)
  • Documentation update (changes to documentation)
  • Performance improvement (non-breaking change which improves efficiency)
  • Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

@welcome
Copy link

welcome bot commented Sep 22, 2025

Thanks for opening this pull request! 🎉 Please check out our contributing guidelines. If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

@arturmelanchyk arturmelanchyk marked this pull request as ready for review September 22, 2025 10:45
@arturmelanchyk arturmelanchyk requested a review from a team as a code owner September 22, 2025 10:45
@gaby gaby added this to v3 Sep 22, 2025
@gaby gaby added this to the v3 milestone Sep 22, 2025
@gaby gaby moved this to In Progress in v3 Sep 22, 2025
@gaby gaby changed the title 🚀 use sync.Pool for fileBuf 🧹 chore: Use sync.Pool for fileBuf Sep 22, 2025
@gaby gaby changed the title 🧹 chore: Use sync.Pool for fileBuf 🧹 chore: Use sync.Pool for Client hooks Sep 22, 2025
@gaby gaby requested a review from Copilot September 22, 2025 11:01
@gaby
Copy link
Member

gaby commented Sep 22, 2025

/gemini review

@gaby
Copy link
Member

gaby commented Sep 22, 2025

@codex review

@codecov
Copy link

codecov bot commented Sep 22, 2025

Codecov Report

❌ Patch coverage is 63.63636% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.42%. Comparing base (edb585b) to head (64ee700).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
client/hooks.go 63.63% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3758      +/-   ##
==========================================
- Coverage   91.44%   91.42%   -0.02%     
==========================================
  Files         113      113              
  Lines       11824    11874      +50     
==========================================
+ Hits        10812    10856      +44     
- Misses        745      750       +5     
- Partials      267      268       +1     
Flag Coverage Δ
unittests 91.42% <63.63%> (-0.02%) ⬇️

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

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 improves performance by introducing sync.Pool for buffer reuse in the parserRequestBodyFile function to reduce memory allocations when processing file uploads. The change shows significant performance improvements with 72.56% reduction in execution time and 99.58% reduction in bytes per operation.

  • Replace direct buffer allocation with pooled buffer management using sync.Pool
  • Add comprehensive benchmark test for measuring file parsing performance
  • Introduce helper functions for benchmark request creation and cleanup

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
client/hooks.go Implements sync.Pool for fileBuf to reuse 1MB buffers across requests
client/hooks_test.go Adds benchmark test and helper functions to measure file parsing performance

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 for file upload buffers, which significantly improves performance by reducing memory allocations, as demonstrated by the new benchmark. The implementation is correct and follows standard practices for using sync.Pool.

My review includes a couple of suggestions for the new benchmark code to align it more closely with the library's existing pooling patterns for File objects. This will improve test consistency and accuracy.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Nice work!

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 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 a sync.Pool-backed 1MB buffer reuse for multipart file handling in client/hooks.go and introduces a benchmark plus test helpers in client/hooks_test.go to exercise parsing multipart/form-data with multiple files.

Changes

Cohort / File(s) Summary of Changes
Buffer pool for uploads
client/hooks.go
Added sync.Pool that provides 1MB []byte buffers; replaced direct 1MB allocations with pooled buffers; added error handling for buffer acquisition; deferred return of buffers to the pool; added sync and errors imports.
Multipart benchmarks and helpers
client/hooks_test.go
Added Benchmark_Parser_Request_Body_File to benchmark parsing of multipart/form-data with multiple files; added test helpers newBenchmarkRequest and releaseBenchmarkRequest; added fasthttp import and updated test lifecycle/resource release logic.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Hooks as client/hooks.go
  participant Pool as sync.Pool
  participant Multipart as Multipart Builder

  Caller->>Hooks: initiate multipart file handling
  Hooks->>Pool: Get() -> *[]byte (1MB)
  alt buffer acquired
    Hooks->>Multipart: build/copy file data using buffer
    Multipart-->>Hooks: multipart body assembled
    Hooks->>Pool: Put(buffer) (deferred)
    Hooks-->>Caller: proceed (success)
  else buffer acquisition failed
    Hooks-->>Caller: return error
  end
  note over Pool,Hooks: Buffer lifecycle: acquire → use → return
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87
  • efectn

Poem

A rabbit nibbles bytes so small,
I fetch a pool and answer the call.
Multipart parcels stacked in a row,
Benchmarks hop — the numbers grow.
I lend my pad, then back they go. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately and concisely summarizes the main change — introducing sync.Pool usage for Client hooks — and is directly related to the changeset; it is a short, single-sentence description that a reviewer can scan and understand. The emoji and "chore:" prefix are stylistic but do not make the title misleading or unclear.
Description Check ✅ Passed The PR description clearly states the purpose and performance impact, includes benchmark results, and fills the template's "Changes introduced" and "Type of change" sections with relevant checks and details about tests and benchmarks. It omits an explicit "Fixes #…" reference and leaves documentation/changelog/migration items unchecked, but those omissions are non-critical for this focused performance change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b71e69 and 64ee700.

📒 Files selected for processing (1)
  • client/hooks.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/hooks.go

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

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)
client/hooks.go (1)

283-304: Always close file readers on error to avoid FD leaks

If io.CopyBuffer or CreateFormFile fails, v.reader isn’t closed, leaking descriptors. Wrap per‑file work in a small closure and defer the close so it runs on both success and error.

-		// If reader is not set, open the file.
-		if v.reader == nil {
-			v.reader, err = os.Open(v.path)
-			if err != nil {
-				return fmt.Errorf("open file error: %w", err)
-			}
-		}
-
-		// Create form file and copy the content.
-		w, err := mw.CreateFormFile(v.fieldName, v.name)
-		if err != nil {
-			return fmt.Errorf("create file error: %w", err)
-		}
-
-		if _, err := io.CopyBuffer(w, v.reader, *fileBuf); err != nil {
-			return fmt.Errorf("failed to copy file data: %w", err)
-		}
-
-		if err := v.reader.Close(); err != nil {
-			return fmt.Errorf("close file error: %w", err)
-		}
+		// Open, copy, and always close the reader (even on error).
+		if err := func(v *File) error {
+			if v.reader == nil {
+				r, err := os.Open(v.path)
+				if err != nil {
+					return fmt.Errorf("open file error: %w", err)
+				}
+				v.reader = r
+			}
+			r := v.reader
+			defer func() { _ = r.Close() }()
+
+			w, err := mw.CreateFormFile(v.fieldName, v.name)
+			if err != nil {
+				return fmt.Errorf("create file error: %w", err)
+			}
+			if _, err := io.CopyBuffer(w, r, buf); err != nil {
+				return fmt.Errorf("failed to copy file data: %w", err)
+			}
+			return nil
+		}(v); err != nil {
+			return err
+		}
🧹 Nitpick comments (4)
client/hooks.go (3)

22-27: Simplify sync.Pool usage; drop pointer indirection and impossible error path

sync.Pool.Get() will invoke New when empty, so the type assertion/len checks are unnecessary. Storing []byte (not *[]byte) avoids an extra indirection and keeps the code simpler.

 var fileBufPool = sync.Pool{
-	New: func() any {
-		b := make([]byte, 1<<20) // 1MB buffer
-		return &b
-	},
+	New: func() any {
+		return make([]byte, 1<<20) // 1MB buffer
+	},
 }
-	fileBuf, ok := fileBufPool.Get().(*[]byte)
-	if !ok || len(*fileBuf) == 0 {
-		return errors.New("failed to retrieve buffer from a sync.Pool")
-	}
-
-	defer fileBufPool.Put(fileBuf)
+	buf := fileBufPool.Get().([]byte)
+	defer fileBufPool.Put(buf)
-		if _, err := io.CopyBuffer(w, v.reader, *fileBuf); err != nil {
+		if _, err := io.CopyBuffer(w, v.reader, buf); err != nil {

Also applies to: 260-266, 297-297


5-5: Remove unused import after pool simplification

After dropping the defensive error path, errors becomes unused.

-	"errors"

22-27: Optional: make buffer size a named const

Improves discoverability/tuning in future perf work.

const fileCopyBufSize = 1 << 20 // 1MB
// ...
var fileBufPool = sync.Pool{ New: func() any { return make([]byte, fileCopyBufSize) } }
client/hooks_test.go (1)

691-718: Report throughput with SetBytes

Expose MB/s by setting processed bytes per op (sum of file payloads). Keeps comparison clearer.

 	b.ReportAllocs()
 
 	for i := 0; i < b.N; i++ {
+		var totalBytes int64
+		for _, c := range fileContents {
+			totalBytes += int64(len(c))
+		}
+		b.SetBytes(totalBytes)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edb585b and 2f87abd.

📒 Files selected for processing (2)
  • client/hooks.go (4 hunks)
  • client/hooks_test.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Format Go code using gofumpt (enforced via make format)
Ensure code passes golangci-lint checks (enforced via make lint)
Optimize struct field alignment using betteralign (enforced via make betteralign)
Modernize Go code using gopls modernize (enforced via make modernize)

Files:

  • client/hooks.go
  • client/hooks_test.go
🧠 Learnings (2)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
PR: gofiber/fiber#2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The project uses the testify/assert package for assertions in unit tests.

Applied to files:

  • client/hooks_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests in this project use testify require.

Applied to files:

  • client/hooks_test.go
🧬 Code graph analysis (2)
client/hooks.go (1)
client/client.go (1)
  • New (632-637)
client/hooks_test.go (1)
client/request.go (10)
  • Request (46-71)
  • FormData (844-846)
  • File (902-907)
  • Header (678-680)
  • AcquireFile (1002-1019)
  • SetFileName (974-978)
  • SetFileFieldName (981-985)
  • SetFileReader (995-999)
  • ReleaseRequest (963-966)
  • ReleaseFile (1023-1026)
🔇 Additional comments (3)
client/hooks_test.go (3)

17-17: LGTM: fasthttp import for bench helpers

Import is justified for Acquire/Release of raw request/args.


720-743: LGTM: request construction via pools

Using AcquireFile and fasthttp Acquire* keeps the benchmark close to production pooling patterns.


745-751: LGTM: proper release of pooled resources — bench verification required

Releasing Request, Args, and Files avoids skewing allocations; code change is correct. Sandbox couldn't run the provided bench/race checks due to errors ("go: cannot find main module" and "failed to start telemetry sidecar") — run the supplied script locally or in CI and attach the test output.

@arturmelanchyk
Copy link
Contributor Author

interesting, coderabbitai review around

22-27: Simplify sync.Pool usage; drop pointer indirection and impossible error path

contradicts to linter requirement

lint: client/hooks.go#L259
SA6002: argument should be pointer-like to avoid allocations (staticcheck)

@arturmelanchyk
Copy link
Contributor Author

in the code I see that both approaches are used, but more common approach is to have pointer-like arguments with result check, so I'll keep my code as is to match this patter.

@gaby
Copy link
Member

gaby commented Sep 23, 2025

@arturmelanchyk I'm pretty sure the pool should be without the pointer, at least in this case

@arturmelanchyk
Copy link
Contributor Author

@gaby fixed

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: 2

🧹 Nitpick comments (1)
client/hooks.go (1)

5-5: Remove unused import after change

errors becomes unused with the simplified pool usage.

-	"errors"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f87abd and 5b71e69.

📒 Files selected for processing (2)
  • client/hooks.go (3 hunks)
  • client/hooks_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/hooks_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Format Go code using gofumpt (enforced via make format)
Ensure code passes golangci-lint checks (enforced via make lint)
Optimize struct field alignment using betteralign (enforced via make betteralign)
Modernize Go code using gopls modernize (enforced via make modernize)

Files:

  • client/hooks.go
🧬 Code graph analysis (1)
client/hooks.go (1)
client/client.go (3)
  • New (632-637)
  • Get (691-693)
  • Put (706-708)
🔇 Additional comments (1)
client/hooks.go (1)

14-14: LGTM

Importing sync is required for the pool.

@gaby
Copy link
Member

gaby commented Sep 26, 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 for file buffers in parserRequestBodyFile to improve performance, which is a great optimization as demonstrated by the provided benchmarks. The implementation is correct and the addition of a benchmark test is excellent for verifying the improvements.

My review includes a few related suggestions to make the sync.Pool usage more idiomatic by pooling []byte directly instead of *[]byte. This is a minor refinement that improves code clarity and maintainability.

@gaby gaby merged commit 0b0a4a4 into gofiber:main Sep 26, 2025
14 of 15 checks passed
@welcome
Copy link

welcome bot commented Sep 26, 2025

Congrats on merging your first pull request! 🎉 We here at Fiber are proud of you! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

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

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants