Skip to content

Conversation

@arturmelanchyk
Copy link
Contributor

@arturmelanchyk arturmelanchyk commented Oct 31, 2025

Description

This PR addresses issue in core::execFunc() where goroutine can potentially write into Response after it has been released back to the pool.

fiber/client/core.go

Lines 75 to 134 in 6ef6de5

func (c *core) execFunc() (*Response, error) {
resp := AcquireResponse()
resp.setClient(c.client)
resp.setRequest(c.req)
done := int32(0)
errCh, reqv := acquireErrChan(), fasthttp.AcquireRequest()
defer releaseErrChan(errCh)
c.req.RawRequest.CopyTo(reqv)
cfg := c.getRetryConfig()
var err error
go func() {
respv := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseRequest(reqv)
fasthttp.ReleaseResponse(respv)
}()
if cfg != nil {
// Use an exponential backoff retry strategy.
err = retry.NewExponentialBackoff(*cfg).Retry(func() error {
if c.req.maxRedirects > 0 && (string(reqv.Header.Method()) == fiber.MethodGet || string(reqv.Header.Method()) == fiber.MethodHead) {
return c.client.DoRedirects(reqv, respv, c.req.maxRedirects)
}
return c.client.Do(reqv, respv)
})
} else {
if c.req.maxRedirects > 0 && (string(reqv.Header.Method()) == fiber.MethodGet || string(reqv.Header.Method()) == fiber.MethodHead) {
err = c.client.DoRedirects(reqv, respv, c.req.maxRedirects)
} else {
err = c.client.Do(reqv, respv)
}
}
if atomic.CompareAndSwapInt32(&done, 0, 1) {
if err != nil {
errCh <- err
return
}
respv.CopyTo(resp.RawResponse)
errCh <- nil
}
}()
select {
case err := <-errCh:
if err != nil {
// Release the response if an error occurs.
ReleaseResponse(resp)
return nil, err
}
return resp, nil
case <-c.ctx.Done():
atomic.SwapInt32(&done, 1)
ReleaseResponse(resp)
return nil, ErrTimeoutOrCancel
}
}

On line 111 when done is set in the if statement:
if atomic.CompareAndSwapInt32(&done, 0, 1) {

and context cancellation happens concurrently at this stage, then atomic.SwapInt32(&done, 1) at line 130:

fiber/client/core.go

Lines 129 to 131 in 6ef6de5

case <-c.ctx.Done():
atomic.SwapInt32(&done, 1)
ReleaseResponse(resp)

will unconditionally overwrite done value and release Response. As a result execution at line 112:
respv.CopyTo(resp.RawResponse)

can still write to Response even when it is returned back to the pool.
Alternatively, if http request fails, then errCh will be released back to the pool while goroutine still trying to write into it:

fiber/client/core.go

Lines 111 to 113 in 6ef6de5

if atomic.CompareAndSwapInt32(&done, 0, 1) {
if err != nil {
errCh <- err

Additionally, by removing unneeded done atomic I achieved ~3% performance boost in single request benchmark and ~8% in parallel.

benchstat old.txt new.txt                                                               
goos: darwin
goarch: arm64
pkg: github.com/gofiber/fiber/v3/client
cpu: Apple M3 Max
                                      │   old.txt   │              new.txt               │
                                      │   sec/op    │   sec/op     vs base               │
_Client_Request-16                      5.008µ ± 0%   4.834µ ± 1%  -3.49% (p=0.000 n=20)
_Client_Request_Parallel-16             2.162µ ± 1%   1.992µ ± 1%  -7.89% (p=0.000 n=20)
_Client_Request_Send_ContextCancel-16   4.652µ ± 1%   5.024µ ± 0%  +7.99% (p=0.000 n=20)
geomean                                 3.693µ        3.643µ       -1.35%

                                      │  old.txt   │              new.txt               │
                                      │    B/op    │    B/op     vs base                │
_Client_Request-16                      217.0 ± 0%   181.0 ± 1%  -16.59% (p=0.000 n=20)
_Client_Request_Parallel-16             218.0 ± 1%   180.0 ± 1%  -17.43% (p=0.000 n=20)
_Client_Request_Send_ContextCancel-16   490.0 ± 0%   479.0 ± 0%   -2.24% (p=0.000 n=20)
geomean                                 285.1        249.9       -12.36%

                                      │  old.txt   │              new.txt               │
                                      │ allocs/op  │ allocs/op   vs base                │
_Client_Request-16                      8.000 ± 0%   6.000 ± 0%  -25.00% (p=0.000 n=20)
_Client_Request_Parallel-16             8.000 ± 0%   6.000 ± 0%  -25.00% (p=0.000 n=20)
_Client_Request_Send_ContextCancel-16   13.00 ± 0%   12.00 ± 0%   -7.69% (p=0.000 n=20)
geomean                                 9.405        7.560       -19.63%

Context cancellation path shows slightly degraded performance because now we also correctly drain error chan that was previously missing

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Warning

Rate limit exceeded

@arturmelanchyk has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 28 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6702b74 and 61b4051.

📒 Files selected for processing (1)
  • client/core_test.go (5 hunks)

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

Replaces atomic-based synchronization in request execution with channel-based coordination and channel pooling; refactors execFunc to use per-call goroutines and pooled response/error channels. Adds tests and a benchmark covering context cancellation, redirect-following with retries, and a blocking transport mock.

Changes

Cohort / File(s) Summary
Core refactor
client/core.go
Reworks execFunc to use pooled channel-based error/response signaling (errChan/respChan) and per-call goroutines. Adds responseChanPool helpers and modifies acquire/release semantics; removes atomic done flags and moves request/response allocation/lifecycle into goroutines. Adjusts timeout/drain logic and integrates with existing retry/redirect paths.
Benchmark
client/client_test.go
Adds Benchmark_Client_Request_Send_ContextCancel to measure behavior when a request is sent with a cancellable context and the context is cancelled mid-send.
Tests & test utilities
client/core_test.go
Adds blockingErrTransport test mock to simulate blocking upstream behavior; adds tests for redirect-following with retry config and for cancellation draining the error channel; extends test imports and synchronization helpers.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller as Client
    participant Exec as execFunc
    participant PoolE as errChanPool
    participant PoolR as respChanPool
    participant Worker as Request Goroutine
    participant Trans as Transport

    Caller->>Exec: Call with context, retries, redirects
    Exec->>PoolE: acquireErrChan()
    Exec->>PoolR: acquireResponseChan()
    Exec->>Worker: spawn with errCh/respCh

    par transport
        Worker->>Trans: perform request
        alt success
            Trans-->>Worker: response
            Worker->>respCh: send response
        else error
            Trans-->>Worker: error
            Worker->>errCh: send error
        end
    and context-monitor
        Caller->>Exec: context cancelled?
        alt cancelled before result
            Exec->>errCh: drain / ignore pending sends
        end
    end

    Worker->>PoolR: releaseResponseChan()
    Worker->>PoolE: releaseErrChan()
    Exec-->>Caller: return response or ErrTimeoutOrCancel / other error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas to review closely:

  • Channel pooling acquire/release semantics to avoid leaks or reuse races
  • Drain logic on context cancellation and interactions with goroutine sends
  • Integration of retry/redirect paths with new channel flow in execFunc
  • Correctness of blockingErrTransport and related test synchronization

Suggested labels

⚡️ Performance

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 I hopped from atoms to channels bright,
pooled little streams that hum through the night.
A cancelled context, a test that rings,
redirects followed on nimble spring legs—
code trimmed and merry, a rabbit's delight. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "🐛 fix: avoid writing into released Response in core::execFunc()" is clearly related to the main change in the changeset. It specifically identifies the bug being fixed (avoiding writes to released Response objects), names the function where the fix occurs (core::execFunc()), and succinctly conveys the primary improvement. The title is concise, specific, and accurately represents the race condition fix described in the detailed PR description and the code changes to client/core.go. The emoji usage is consistent with the repository's documented commit formatting guidelines.
Description Check ✅ Passed The pull request description is mostly complete and well-structured according to the template. It includes a thorough explanation of the problem with specific references to affected code lines, a clear description of the race condition scenario, and comprehensive benchmark results demonstrating performance improvements (~3% for single requests, ~8% for parallel requests) with detailed allocation reductions. The author properly categorized the change as both an Enhancement and Performance improvement, and marked "Benchmarks" in the Changes introduced section. However, the description omits the "Fixes #" issue reference at the beginning of the description, which is a standard convention in the template but represents a relatively minor gap given the overall completeness and clarity of the substantive content.

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.

@codecov
Copy link

codecov bot commented Oct 31, 2025

Codecov Report

❌ Patch coverage is 94.44444% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.26%. Comparing base (6ef6de5) to head (61b4051).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
client/core.go 94.44% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3830   +/-   ##
=======================================
  Coverage   92.25%   92.26%           
=======================================
  Files         115      115           
  Lines        9734     9745   +11     
=======================================
+ Hits         8980     8991   +11     
  Misses        480      480           
  Partials      274      274           
Flag Coverage Δ
unittests 92.26% <94.44%> (+<0.01%) ⬆️

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.

@arturmelanchyk arturmelanchyk changed the title fix: avoid writing into released Response in core::execFunc() 🐛 fix: avoid writing into released Response in core::execFunc() Oct 31, 2025
@arturmelanchyk arturmelanchyk force-pushed the exec-func branch 3 times, most recently from 2d79c28 to 70aeab8 Compare October 31, 2025 20:51
@arturmelanchyk arturmelanchyk marked this pull request as ready for review November 1, 2025 10:52
@arturmelanchyk arturmelanchyk requested a review from a team as a code owner November 1, 2025 10:52
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)
client/core.go (1)

233-237: Clarify panic text for response channel assertion.

The panic string still says *Response, even though the type assertion is for chan *Response, which can send someone debugging pool corruption down the wrong path. Please update the message to reflect the actual type being asserted.

-		panic(errors.New("failed to type-assert to *Response"))
+		panic(errors.New("failed to type-assert to chan *Response"))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ef6de5 and f7303ac.

📒 Files selected for processing (3)
  • client/client_test.go (1 hunks)
  • client/core.go (3 hunks)
  • client/core_test.go (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Apply formatting using gofumpt (Make target: format)
Optimize struct field alignment using betteralign (Make target: betteralign)
Modernize code using gopls modernize (Make target: modernize)

Files:

  • client/core.go
  • client/client_test.go
  • client/core_test.go
🧠 Learnings (10)
📓 Common learnings
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
📚 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:

  • client/core.go
  • client/core_test.go
📚 Learning: 2024-11-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.

Applied to files:

  • client/client_test.go
  • client/core_test.go
📚 Learning: 2024-07-26T21:00:12.902Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-07-26T21:00:12.902Z
Learning: The project uses the testify/assert package for assertions in unit tests.

Applied to files:

  • client/core_test.go
📚 Learning: 2024-10-02T23:03:31.727Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-02T23:03:31.727Z
Learning: Unit tests in this project use testify require.

Applied to files:

  • client/core_test.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • client/core_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.

Applied to files:

  • client/core_test.go
📚 Learning: 2024-10-12T10:01:44.206Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/middleware_test.go:190-191
Timestamp: 2024-10-12T10:01:44.206Z
Learning: When testing session `IdleTimeout` expiration, it's acceptable to use `time.Sleep` to simulate the passage of time in tests.

Applied to files:

  • client/core_test.go
📚 Learning: 2024-09-25T16:18:34.719Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/config.go:122-122
Timestamp: 2024-09-25T16:18:34.719Z
Learning: In `DefaultErrorHandler(c *fiber.Ctx, err error)`, since `c` is a pointer to an interface, we need to dereference `*c` when calling interface methods like `SendStatus`.

Applied to files:

  • client/core_test.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.

Applied to files:

  • client/core_test.go
🧬 Code graph analysis (3)
client/core.go (2)
client/request.go (2)
  • AcquireRequest (983-989)
  • ReleaseRequest (993-996)
client/response.go (3)
  • AcquireResponse (197-203)
  • ReleaseResponse (207-210)
  • Response (19-25)
client/client_test.go (3)
client/client.go (1)
  • New (738-743)
client/request.go (3)
  • AcquireRequest (983-989)
  • Request (46-73)
  • ReleaseRequest (993-996)
client/core.go (1)
  • ErrTimeoutOrCancel (281-281)
client/core_test.go (4)
client/client.go (4)
  • Get (817-819)
  • New (738-743)
  • Config (641-655)
  • Client (37-67)
client/request.go (3)
  • AcquireRequest (983-989)
  • Header (708-710)
  • Request (46-73)
client/core.go (2)
  • RetryConfig (31-31)
  • ErrTimeoutOrCancel (281-281)
client/response.go (1)
  • Response (19-25)

@gaby gaby requested a review from Copilot November 1, 2025 14:22
@gaby
Copy link
Member

gaby commented Nov 1, 2025

/gemini review

@codex review

@gaby gaby added the v3 label Nov 1, 2025
@gemini-code-assist
Copy link
Contributor

Thank you for invoking the review, @gaby! I've completed my review of this pull request.

This PR effectively addresses a critical race condition in core::execFunc() by refactoring the request execution to use channel-based synchronization instead of atomic operations. This change prevents potential issues where a goroutine could write to a Response object after it has been released back to the pool, or an errCh could be released prematurely.

Overall Assessment

This is a well-executed refactor that improves the correctness and robustness of the client library. The introduction of channel pooling for response and error channels is a good practice for resource management in Go. The new test utilities and benchmark additions are valuable for validating the changes and measuring performance.

Review Highlights

  • Correctness: The core issue of the race condition is directly addressed by moving to channel-based synchronization, ensuring proper handling of responses and errors, especially during context cancellation.
  • Efficiency: The PR description notes a performance boost for single and parallel requests, which is a great side effect of the refactor. The slight degradation in context cancellation performance is explained by the correct draining of the error channel, indicating a more robust implementation.
  • Maintainability: The use of sync.Pool for channels (responseChanPool, errChanPool) is a good Go idiom for reducing allocations and improving performance. The new blockingErrTransport test utility is a valuable addition for testing concurrent scenarios.
  • Test Coverage: The addition of Benchmark_Client_Request_Send_ContextCancel and new test cases in client/core_test.go (e.g., follow redirect with retry config, cancel drains errChan) significantly improves the test coverage for the new synchronization logic and edge cases.

Minor Suggestion

  • client/core.go (Line 261): In the acquireErrChan function, the panic message for type assertion failure currently states errors.New("failed to type-assert to error"). For consistency and clarity, it would be more accurate to specify chan error as the expected type, similar to acquireResponseChan.
		panic(errors.New("failed to type-assert to chan error"))

Overall, this is a strong pull request that significantly improves the reliability and performance of the client. Great work!

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 refactors the execFunc method to fix a concurrency issue related to context cancellation and channel draining. The key changes replace the atomic flag pattern with separate response and error channels, ensuring proper cleanup when a request is canceled.

  • Replaces atomic done flag with separate respChan and errChan for cleaner concurrency handling
  • Adds a goroutine to drain channels and release resources when context is canceled
  • Changes channel buffers from buffered to unbuffered for both error and response channels

Reviewed Changes

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

File Description
client/core.go Refactored execFunc() to use separate response and error channels with proper draining on context cancellation; added response channel pool infrastructure
client/core_test.go Added tests for redirect with retry config and context cancellation draining; introduced blockingErrTransport mock for testing concurrent scenarios
client/client_test.go Added benchmark test for context cancellation to measure performance impact of the new draining mechanism

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ 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
  • 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 address that feedback".

@gaby
Copy link
Member

gaby commented Nov 1, 2025

@arturmelanchyk Can you fix the notes for client/core.go. I'm not worried about the tests comment.

arturmelanchyk and others added 3 commits November 1, 2025 15:40
Signed-off-by: Artur Melanchyk <[email protected]>
@arturmelanchyk arturmelanchyk merged commit 7a17195 into gofiber:main Nov 1, 2025
17 of 21 checks passed
gaby added a commit that referenced this pull request Nov 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants