Skip to content

šŸ”„ feat: added cancelFunc and async goroutine to monitore lost of connec… - #4404

Open
dima11223432 wants to merge 14 commits into
gofiber:mainfrom
dima11223432:fix/cancellation-chain
Open

šŸ”„ feat: added cancelFunc and async goroutine to monitore lost of connec…#4404
dima11223432 wants to merge 14 commits into
gofiber:mainfrom
dima11223432:fix/cancellation-chain

Conversation

@dima11223432

Copy link
Copy Markdown

Description

This PR fixes a critical resource leak and testing issue where the background monitoring goroutine for client-disconnect detection could get stuck in a select block indefinitely.

When a middleware (like timeout) abandons the original request context or a context panics after a timeout, the fasthttp.RequestCtx might never close its connection channel in test environments.

The solution ensures the lifecycle of the connection-monitoring goroutine is strictly bound to the local request context (reqCtx.Done()), allowing it to gracefully exit the moment the context is canceled or reset by the Fiber engine.

Fixes #4338
Changes introduced

Robust Goroutine Lifecycle: Updated the connection-monitoring go func() inside DefaultCtx.Reset to actively listen to reqCtx.Done().

Prevented Memory Leaks: Guaranteed that resetting the context or releasing it back to the pool (release()) immediately shuts down any pending background monitoring.

Flawless Test Execution: Fixed the pseudo-FAIL state in middleware/timeout tests caused by asynchronous post-timeout panics colliding with the test runner's cleanup phase.

Type of change

[x] Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

[x] Conducted a self-review of the code and provided comments for complex or critical parts.

[x] Added or updated unit tests to validate the effectiveness of the changes or new features.

[x] Ensured that new and existing unit tests pass locally with the changes.

@dima11223432
dima11223432 requested a review from a team as a code owner June 5, 2026 12:39
@welcome

welcome Bot commented Jun 5, 2026

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Too much diff to scan? Review this PR in Change Stack to start with the highest-impact changes.

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ā–¶ļø Resume reviews
  • šŸ” Trigger review

Walkthrough

DefaultCtx gains a cancelFunc field. Context() reads or lazily creates a cancellable request context in fasthttp user values and may spawn a goroutine to cancel it when the connection or context finishes. Reset() and release() cancel and clear stored cancel functions. Tests exercise lazy init, disconnect cancellation, and timeout integration.

Changes

Request-scoped context cancellation

Layer / File(s) Summary
Add cancelFunc field and Context() behavior
ctx.go
DefaultCtx adds a cancelFunc field. Context() reads a context.Context from fasthttp user values (creates one with context.WithCancel if missing), stores it via SetContext, and may spawn a goroutine that cancels the stored context when fasthttp.Done() or the stored context’s Done() fires.
Reset() creates/stores new user context
ctx.go
Reset() cancels and clears any previously stored cancelFunc. When fctx is non-nil, Reset() creates a new cancellable background context, stores it in fasthttp user values under the user context key, marks the user context as set, and records the new cancel function in c.cancelFunc.
release() lifecycle cleanup
ctx.go
release() cancels and clears any stored cancelFunc before performing request cleanup, including clearing fasthttp user context.
Tests: lazy init, cancel-on-disconnect, timeout integration
ctx_test.go, middleware/timeout/timeout_test.go
Adds Test_Ctx_Context_Lazy_Initialization_Suite and Test_Ctx_Context_Cancel_On_Disconnect covering lazy Context() initialization and disconnect-triggered cancellation; adds TestTimeout_Integration_WithLazyContext and TestTimeout_Integration_PanicAfterTimeoutWithLazyCtx to validate timeout middleware behavior with lazy contexts and reclamation after panics.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DefaultCtx as DefaultCtx.Context()
  participant Fasthttp as fasthttp.RequestCtx
  participant Canceller as CancellationGoroutine
  Client->>DefaultCtx: call Context()
  DefaultCtx->>Fasthttp: read user context (user values)
  alt no context
    DefaultCtx->>DefaultCtx: context.WithCancel(background)
    DefaultCtx->>Fasthttp: SetContext(newCtx)
  end
  DefaultCtx->>Canceller: spawn goroutine (if connection present)
  Canceller->>Fasthttp: wait on Fasthttp.Done()
  Canceller->>DefaultCtx: wait on stored ctx.Done()
  alt fasthttp done first
    Canceller->>DefaultCtx: call cancelFunc()
    Canceller->>DefaultCtx: clear cancelFunc
  else ctx done first
    Canceller->>Canceller: return
  end
Loading

Estimated code review effort

šŸŽÆ 4 (Complex) | ā±ļø ~45 minutes

Possibly related PRs

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 I nibble at deadlines, cancel with grace,
a tiny goroutine guards each request's space.
Lazy sprouts grow when handlers call,
then Reset clears crumbs and tidies all.
Hop, tidy, sleep — contexts safe in place.

🚄 Pre-merge checks | āœ… 5
āœ… Passed checks (5 passed)
Check name Status Explanation
Description check āœ… Passed The description adequately covers the problem, solution, and impact; however, required checklist items for documentation updates and benchmarks are not addressed.
Docstring Coverage āœ… Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check āœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check āœ… Passed Check skipped because no linked issues were found for this pull request.
Title check āœ… Passed The title matches the main change: adding cancelFunc handling and async disconnect monitoring in the context lifecycle.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Copy link
Copy Markdown
Contributor

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 context cancellation handling in DefaultCtx by adding a cancelFunc field, managing context cancellation inside Context(), and resetting/canceling it during Reset(). However, the reviewer identified critical issues with these changes: the background goroutine and 10ms ticker introduced in Reset() cause severe performance regressions, data races, and goroutine/CPU leaks. Additionally, the background goroutine in Context() introduces a potential deadlock, goroutine leak, and data race. The reviewer recommends removing the background goroutine from Reset() entirely and provides a refactored implementation for Context() to safely manage the context lifecycle.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ctx.go
Comment thread ctx.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

šŸ¤– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ctx.go`:
- Around line 717-738: The watcher goroutine in DefaultCtx.Reset reads
c.fasthttp concurrently, causing a race and potential nil deref; change the
goroutine to capture the per-request *fasthttp.RequestCtx into a local (e.g.
fctx := c.fasthttp) and use fctx.UserValue(userContextKey) instead of reading
c.fasthttp on each tick, and modify release() to call and nil c.cancelFunc
(invoke the stored cancel function and set c.cancelFunc = nil) so the ticker
goroutine is deterministically stopped when the context is returned to the pool.
šŸŖ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ā„¹ļø Review info
āš™ļø Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c158d94b-b512-48cf-8b19-a09d3b07ee5d

šŸ“„ Commits

Reviewing files that changed from the base of the PR and between 5fe6c52 and 20dddea.

šŸ“’ Files selected for processing (1)
  • ctx.go

Comment thread ctx.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

āš ļø Outside diff range comments (1)
ctx.go (1)

143-155: āš ļø Potential issue | šŸ”“ Critical | ⚔ Quick win

Multiple goroutines spawned per request and race condition on cancelFunc.

Two issues with the goroutine spawn logic:

  1. Multiple goroutines per request: The goroutine spawn block runs on every Context() call when c.cancelFunc != nil. Since cancelFunc is set in Reset() and never cleared after spawning, each subsequent call to Context() spawns another goroutine doing the same work.

  2. Race on cancelFunc capture: Between the check c.cancelFunc != nil (line 143) and the capture cancel := c.cancelFunc (line 146), another goroutine could call Reset()/release() setting c.cancelFunc = nil. The captured cancel becomes nil, causing a panic when the goroutine calls cancel().

šŸ› Suggested fix: atomically claim cancelFunc when spawning
-	if c.cancelFunc != nil && c.fasthttp.Conn() != nil {
-		fastHttpDone := c.fasthttp.Done()
-		reqCtx := ctx
-		cancel := c.cancelFunc
-
-		go func() {
-			select {
-			case <-fastHttpDone:
-				cancel()
-			case <-reqCtx.Done():
-			}
-		}()
-	}
+	// Atomically claim the cancelFunc to spawn the monitoring goroutine only once.
+	// Once claimed (set to nil here), subsequent Context() calls won't spawn duplicates.
+	if cancel := c.cancelFunc; cancel != nil && c.fasthttp.Conn() != nil {
+		c.cancelFunc = nil // Claim: prevent duplicate goroutines
+		fastHttpDone := c.fasthttp.Done()
+		reqCtx := ctx
+
+		go func() {
+			select {
+			case <-fastHttpDone:
+				cancel()
+			case <-reqCtx.Done():
+			}
+		}()
+	}

Note: If cancelFunc is cleared in Context(), ensure Reset() and release() still check for nil before calling, which they already do. However, this means calling c.cancelFunc() in Reset()/release() may not cancel an in-flight monitoring goroutine if Context() already claimed it. Consider whether the goroutine's cancel should still be reachable for explicit cleanup, or if relying on reqCtx.Done() is sufficient.

šŸ¤– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ctx.go` around lines 143 - 155, The monitoring goroutine in Context() can be
spawned repeatedly and races on c.cancelFunc; fix by atomically claiming and
clearing the cancel function before starting the goroutine so only one monitor
is created and you never capture a nil cancel. Modify the Context() logic
(referencing Context(), c.cancelFunc, c.fasthttp.Done()) to acquire the cancel
function under the same synchronization used by Reset()/release() (or use an
atomic compare-and-swap on a pointer to the cancel func) and set c.cancelFunc =
nil as you claim it, then spawn the goroutine with the claimed non-nil cancel;
leave Reset()/release() defensive (check for nil) as they already do.
šŸ¤– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@ctx.go`:
- Around line 143-155: The monitoring goroutine in Context() can be spawned
repeatedly and races on c.cancelFunc; fix by atomically claiming and clearing
the cancel function before starting the goroutine so only one monitor is created
and you never capture a nil cancel. Modify the Context() logic (referencing
Context(), c.cancelFunc, c.fasthttp.Done()) to acquire the cancel function under
the same synchronization used by Reset()/release() (or use an atomic
compare-and-swap on a pointer to the cancel func) and set c.cancelFunc = nil as
you claim it, then spawn the goroutine with the claimed non-nil cancel; leave
Reset()/release() defensive (check for nil) as they already do.

ā„¹ļø Review info
āš™ļø Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8346da39-823c-4ada-8c67-2c31131f2e97

šŸ“„ Commits

Reviewing files that changed from the base of the PR and between 20dddea and 4e77e71.

šŸ“’ Files selected for processing (1)
  • ctx.go

@gaby

gaby commented Jun 6, 2026

Copy link
Copy Markdown
Member

@dima11223432 Please add unit-tests/docs before submitting a PR.

@gaby gaby closed this Jun 6, 2026
@github-project-automation github-project-automation Bot moved this to Done in v3 Jun 6, 2026
@dima11223432

Copy link
Copy Markdown
Author

Can i write only unit tests? :)

@ReneWerner87

Copy link
Copy Markdown
Member

Please test your code locally first and add unit tests to it before creating pull requests marked as "done"
We can reopen it ,when the pr is in a clean tested state

@dima11223432

Copy link
Copy Markdown
Author

@ReneWerner87 I have written tests for cancelFunc! Pls, reopen pr

@ReneWerner87 ReneWerner87 reopened this Jun 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

šŸ¤– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ctx_test.go`:
- Around line 9601-9711: Add t.Parallel() at the start of the top-level tests
and each subtest to comply with the repository rule: insert t.Parallel() as the
first statement in Test_Ctx_Context_Lazy_Initialization_Suite, inside each t.Run
callback ("Lazy_Initialization_With_Conn", "Lazy_Initialization_Without_Conn",
"Release_Without_Context_Access"), and at the start of
Test_Ctx_Context_Cancel_On_Disconnect so the tests and subtests run in parallel.
- Around line 9680-9708: Test_Ctx_Context_Cancel_On_Disconnect is cancelling the
injected netCtx directly instead of exercising the disconnect path in
DefaultCtx.Context(); instead of calling cancelNet(), arrange for the fasthttp
Done signal to fire so the code path that checks c.cancelFunc &&
c.fasthttp.Conn() is exercised: create a fasthttp.RequestCtx (or a small test
double) whose Done() returns a cancellable channel (e.g., create a
context.WithCancel and wire its Done into the RequestCtx/Done return), assign
that RequestCtx to c.fasthttp and ensure c.cancelFunc is non-nil (via
c.SetContext or by setting cancelFunc like in production), then trigger the
RequestCtx cancellation (call the cancel returned by context.WithCancel) and
assert goCtx.Done() is closed and goCtx.Err() is context.Canceled rather than
cancelling netCtx directly.
šŸŖ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ā„¹ļø Review info
āš™ļø Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c7d6b547-d1e4-4818-958d-5446b93968d4

šŸ“„ Commits

Reviewing files that changed from the base of the PR and between 03f1e0b and f55f5c4.

šŸ“’ Files selected for processing (2)
  • ctx_test.go
  • middleware/timeout/timeout_test.go

Comment thread ctx_test.go
Comment thread ctx_test.go
@ReneWerner87

ReneWerner87 commented Jun 6, 2026

Copy link
Copy Markdown
Member

Tests and lint are falling

@dima11223432

Copy link
Copy Markdown
Author

I have resolved linter errors and data race

@gaby gaby changed the title feat: added cancelFunc and async goroutine to monitore lost of connec… šŸ”„ feat: added cancelFunc and async goroutine to monitore lost of connec… Jun 27, 2026
@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

āŒ Patch coverage is 72.41379% with 8 lines in your changes missing coverage. Please review.
āœ… Project coverage is 92.92%. Comparing base (adef0e8) to head (3a9ab44).
āš ļø Report is 340 commits behind head on main.

Files with missing lines Patch % Lines
ctx.go 72.41% 6 Missing and 2 partials āš ļø
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4404      +/-   ##
==========================================
- Coverage   93.01%   92.92%   -0.09%     
==========================================
  Files         139      139              
  Lines       13789    13813      +24     
==========================================
+ Hits        12826    12836      +10     
- Misses        596      607      +11     
- Partials      367      370       +3     
Flag Coverage Ī”
unittests 92.92% <72.41%> (-0.09%) ā¬‡ļø

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

ā˜” View full report in Codecov by Harness.
šŸ“¢ 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.

@gaby

gaby commented Aug 10, 2026

Copy link
Copy Markdown
Member

@dima11223432 Fix merge conflict

@dima11223432

Copy link
Copy Markdown
Author

@dima11223432 Fix merge conflict

Okey, i will do it today

gaby commented Aug 17, 2026

Copy link
Copy Markdown
Member

Status

0 of 5 review threads are formally unresolved — but two of them were marked resolved without the code actually changing, and the central mechanism does not work as intended. The PR is also mergeable_state: blocked with the merge conflict @gaby flagged on Aug 10 still outstanding, and no test/lint check runs have completed on head (2075ffd). Not close to mergeable.

Remaining review comments

All five threads are is_resolved: true; verified each against head 2075ffd:

  • gemini — ticker goroutine in Reset (perf/race/leak). Addressed: the 10ms ticker is gone.
  • gemini — deadlock/goroutine leak/race in Context(). Not addressed. The suggested snippet was applied but with c.cancelFunc = nil added (ctx.go:154), which reintroduces the exact leak the comment warned about. See finding 2 below.
  • coderabbit (outdated) — capture fctx locally, cancel in release(). Addressed for the capture; the release() cancel is present but unreachable in practice (finding 3).
  • coderabbit — add t.Parallel() to the new tests/subtests. Not addressed. Neither Test_Ctx_Context_Lazy_Initialization_Suite, its three subtests, nor Test_Ctx_Context_Cancel_On_Disconnect call t.Parallel() at head. tparallel is enabled in .golangci.yml:62, so this will fail lint.
  • coderabbit — Test_Ctx_Context_Cancel_On_Disconnect cancels the injected context, not disconnect. Not addressed; the test is byte-identical at head. The claim is correct — see finding 9.

Separately, coderabbit's "outside diff range" note about spawning a goroutine on every Context() call was addressed, by nil-ing cancelFunc — which is what causes finding 2.

Additional findings

1. fasthttp.RequestCtx.Done() is not a client-disconnect signal — the feature does not do what the PR claims.
In fasthttp v1.73.0 (the version in go.mod):

func (ctx *RequestCtx) Done() <-chan struct{} { return ctx.s.done }
// Note: Because creating a new channel for every request is just too expensive, so
// RequestCtx.s.done is only closed when the server is shutting down.

s.done is allocated only in Server.Serve and closed only by Shutdown. It is never closed on client disconnect. ServeConn and fakeServer leave it nil, so under app.Test the receive is on a nil channel and blocks forever. The watcher in ctx.go:156-162 therefore only ever fires on server shutdown. Detecting a lost connection needs a different mechanism (e.g. ConnState/Conn() read probe), not Done().

2. Permanent goroutine leak — one per request that calls Context().
Context() hands cancel to the goroutine and then sets c.cancelFunc = nil (ctx.go:153-154). After that:

  • release() (ctx.go:772) and the next Reset() (ctx.go:736) both see nil and cancel nothing.
  • The goroutine's only exits are fastHTTPDone (server shutdown only, per finding 1) and reqCtx.Done() — but cancel is now owned exclusively by that goroutine, so nothing else can ever cancel reqCtx.

Net: the goroutine, its closure, and a context.cancelCtx survive until process shutdown, for every request that touches c.Context(). Fiber calls c.Context() internally (e.g. SaveFileToStorage), and the timeout middleware does parent := ctx.Context(), so this is not a rare path. This is unbounded growth under load, worse than the ticker version it replaced.

3. release() cancellation semantics are inverted.
Because Context() claims the cancel func, the request context is cancelled on release only when Context() was never called (or when Conn() is nil). Handlers that use the context get one that is never cancelled; handlers that ignore it get one that is. That is the opposite of the intent.

4. The watcher can be wired to the wrong context.
If anything calls SetContext() before the first Context() call, Context() reads the caller's context into ctx/reqCtx, but cancel still belongs to the orphaned context created in Reset(). The goroutine then waits on the caller's context and, when it fires, cancels a context no one holds. So even with a working disconnect signal, cancellation would not propagate. middleware/timeout/timeout.go:34-36 happens to call Context() first, but the reverse order is a documented, supported pattern.

5. Reset() is eager, not lazy — new per-request cost on the hot path.
ctx.go:754-759 unconditionally does context.WithCancel(context.Background()) + SetUserValue + isUserContextSet = true on every request, contradicting the "lazy initialization" framing in the description and the test names. That is an extra allocation plus a userValues append per request, and it also forces release() to always take the isUserContextSet branch (ctx.go:776-780). No benchmark numbers are included. Given the cost and the change to request-context lifetime, this should be lazy and/or gated behind a fiber.Config flag.

6. An unrelated existing test was deleted.
Test_Ctx_Cookie_DoesNotMutateArgument is present on main at ctx_test.go:10758 but does not exist at head — the new tests were written over it. This looks like a bad conflict resolution and must be restored.

7. SetContext lost its doc comment.
The diff removes // SetContext sets a context implementation by user. above ctx.go:168. ctx_interface_gen.go:29 still carries that comment, so the ifacemaker-generated file now drifts from source, and revive's exported-comment rule will flag the method.

8. Dead nil-guard in Reset().
if fctx != nil at ctx.go:749 is unreachable protection — c.pathOriginal = c.app.toString(fctx.URI().PathOriginal()) two lines earlier at ctx.go:747 already dereferences fctx. Either guard the whole function or drop both checks.

9. The tests do not cover the feature.

  • Test_Ctx_Context_Cancel_On_Disconnect uses a bare &fasthttp.RequestCtx{}, so Conn() is nil and the watcher goroutine is never spawned. It only proves that a context you cancel yourself is cancelled.
  • Lazy_Initialization_With_Conn calls fctx.Init(...), which sets ctx.s = fakeServer (whose done is nil), so it does spawn a goroutine that blocks forever — the test leaks it.
  • No test drives the actual disconnect → cancel path, which is consistent with codecov's 72% patch coverage on ctx.go.
  • Style: raw t.Fatal/t.Error instead of require, unlike the rest of ctx_test.go.

10. Residual race on c.cancelFunc (lower confidence).
cancelFunc is a plain struct field read/written by Context(), Reset() and release(). Handlers that call c.Context() from a spawned goroutine after Abandon() — a pattern the timeout and SSE middlewares enable — can race with release(). The reclaim latch covers the timeout path, but the field itself has no synchronization. Worth running the new tests under -race with a keep-alive server, not just app.Test.


Generated by Claude Code

@ReneWerner87

Copy link
Copy Markdown
Member

Thank you for staying with this over so many rounds. I owe you a straight answer on why, rather than another round of review.

The watcher selects on c.fasthttp.Done(). fasthttp documents that channel in server.go:

// Note: Because creating a new channel for every request is just too expensive, so
// RequestCtx.s.done is only closed when the server is shutting down.

It fires on server shutdown, not on client disconnect, so the goroutine cannot observe the event this PR exists to detect. net/http gets it from startBackgroundRead, a concurrent one-byte read on the socket; fasthttp has no concurrent reader. valyala/fasthttp#965 is the upstream thread on this, and as of v1.73.0 the behaviour above is unchanged. We closed #4338 as not planned on 2026-07-28 for the same reason.

Two side effects I measured at head 2075ffde:

  • Reset now builds a context.WithCancel on every request, whether or not Context() is ever called. Against main: 11.4 ns/op, 0 B, 0 allocs becomes 80 ns/op, 96 B, 2 allocs.
  • Context() sets c.cancelFunc = nil before spawning, so release() and the next Reset() both no-op and the goroutine holds the only remaining cancel. It exits on server shutdown, which pins a goroutine and a cancelCtx per request.

If you need a per-request cancellation signal, fasthttp is the place to move it. A framework-level watcher cannot reach the socket.

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.

šŸ› [Bug]: Context.Background() used as default in request path — breaks cancellation chain

3 participants