Skip to content

⚡ perf: adopt gofiber/utils v2.4.0 helpers and optimize adaptor/proxy hot paths#4557

Merged
ReneWerner87 merged 11 commits into
mainfrom
claude/gofiber-utils-helpers-ia74zz
Jul 24, 2026
Merged

⚡ perf: adopt gofiber/utils v2.4.0 helpers and optimize adaptor/proxy hot paths#4557
ReneWerner87 merged 11 commits into
mainfrom
claude/gofiber-utils-helpers-ia74zz

Conversation

@gaby

@gaby gaby commented Jul 24, 2026

Copy link
Copy Markdown
Member

Description

Adopts the helper functions introduced in gofiber/utils v2.4.0 (ParseHTTPDate/AppendHTTPDate, ParseIPv4/ParseIPv6, AppendQueryEscape) across every hot path in the framework where they beat the stdlib/fasthttp equivalent, and follows up with targeted optimizations of the proxy and adaptor middlewares plus two correctness fixes surfaced along the way. Every conversion is either provably behavior-identical or an explicitly documented, test-pinned RFC 9110 compliance improvement.

Benchmarks below were taken on the CI-like environment (Intel Xeon @ 2.10 GHz, linux/amd64, -count=3); absolute numbers are noisy on that VM, the before/after deltas are what matters.

Changes introduced

HTTP date parsing/formatting (req.go, middleware/cache)

  • parseHTTPDate (backs Fresh()/If-Modified-Since) now delegates to utils.ParseHTTPDate: net/http.ParseTime semantics (IMF-fixdate + obsolete RFC 850/asctime, RFC 9110 §5.6.7) behind an allocation-free scalar fast path, dropping the string(date) conversion.
  • The cache middleware writes the Date header with utils.AppendHTTPDate (direct field writer) instead of fasthttp.AppendHTTPDate (which delegates to time.AppendFormat), on both the cache-hit and cache-store paths.
  • The cache middleware parses Date and Expires with utils.ParseHTTPDate, so both share one RFC 9110 acceptance set. Previously a response dating itself consistently in RFC 850 got its Date honored while its Expires was routed to the parse-error/force-revalidate path. Pinned by Test_CacheExpiresObsoleteFormatAllowsCaching (fails on the old parser).
  • utils.ParseHTTPDate tolerates surrounding ASCII whitespace (RFC 9110 §5.5 OWS exclusion), which net/http.ParseTime does not; this is documented on parseHTTPDate and pinned by a Fresh() test case.
Benchmark Before After
Benchmark_Ctx_Fresh_LastModified 229 ns/op 208 ns/op (−9%)
AppendHTTPDate (Date header rendering, micro) 150 ns/op 39 ns/op (−74%)

IP parsing (req.go, middleware/hostauthorization, client)

  • isTrustedProxyIP uses utils.ParseIPv4/ParseIPv6 instead of netip.ParseAddr — identical acceptance, no error allocation for invalid client-supplied addresses.
  • net.ParseIP is netip.ParseAddr minus zoned addresses (Go ≥1.17), so hostauthorization's IPv6 Host validation and the client cookie jar's isIPLiteral now screen % and delegate to the utils parsers: identical acceptance, zero allocations on either outcome. Pinned by tests covering zoned rejection, IPv4-mapped IPv6, bracketed literals, and malformed octets.

Query escaping (redirect.go, middleware/cache, middleware/paginate)

  • Redirect.Route escapes query values straight into the pooled buffer via utils.AppendQueryEscape.
  • The cache key generator escapes query keys/values in place, dropping two intermediate strings per parameter pair; the buffer bound check is restated post-append in an exactly equivalent form.
  • Paginate renders page URLs through an url.Values.Encode-equivalent that escapes into a single buffer; output is pinned byte-for-byte against Encode across escaping-heavy inputs.
Benchmark Before After
Benchmark_Redirect_Route_WithQueries 275 ns/op 256 ns/op (−7%)
Benchmark_defaultKeyGenerator/multiparam ~893 ns/op ~819 ns/op (−8%)

Adaptor middleware (middleware/adaptor)

  • handlerFunc (the FiberHandlerFunc/FiberApp serving path) builds the fasthttp request directly into the pooled RequestCtx instead of filling a standalone Request and CopyTo-ing it across — Init2 only touches connection metadata and buffer flags, so in-place construction is fasthttp's own Init order minus the full header+body copy.
  • The noopConn is pooled together with the RequestCtx and the stateless logger is shared, removing two per-request allocations.
  • resolveRemoteAddr takes a fast path for numeric ip:port literals (the only form net/http servers produce) via utils.ParseIPv4/ParseIPv6 + a strict decimal port parse, skipping net.ResolveTCPAddr's resolver machinery; hostnames/service ports still fall back. A test pins the fast path to ResolveTCPAddr's results (IPv6 zones, leading-zero forms included).
  • 🐛 Bug fix: the header copy loops in handlerFunc and HTTPMiddleware called Header.Set once per value, collapsing repeated header lines (e.g. two X-Forwarded-For entries) to the last value. Now Set for the first value + Add for the rest; fasthttp's singleton headers (Cookie/Content-Type/…) keep their by-design single-value behavior. New tests pin both paths and fail against the old code.
Benchmark Before After
FiberHandlerFunc, fresh 1 MB body per request ~189 µs/op, 33 allocs ~123 µs/op, 29 allocs (−35%)
Benchmark_FiberHandlerFunc/No_Content 8 allocs/op 7 allocs/op

Proxy middleware (middleware/proxy)

  • Connection-listed hop-by-hop headers are stripped by iterating the header value bytes in place (no string copy, no name slice); runs twice per proxied request (request + response direction). The names alias the value buffers, which is sound because fasthttp's Del re-slices the entry list without rewriting other entries' buffers; the fuzz harness now exercises deletion against a real header to guard exactly that invariant.
  • followRedirects renders each hop's URL once instead of twice, removing a URL.String() allocation per redirect hop.
Benchmark Before After
BenchmarkStripHopByHop_WithConnection 1565 ns/op, 72 B, 3 allocs ~958 ns/op, 0 B, 0 allocs (−39%)
BenchmarkStripHopByHop_NoConnection ~706 ns/op, 0 allocs unchanged

Deliberately not converted (each verified): url.PathUnescape in extractors/static (stdlib's no-% fast path returns the original string with zero allocations), startup-only net.ParseIP/ParseCIDR in app.go, proxy SSRF checks (interoperate with net.IP through the resolver; dial/DNS dominates), res.go encodeExtValue (RFC 8187 attr-char set differs from any utils helper), and route constraints' time.Parse (user-defined layouts).

  • Benchmarks: before/after tables above; microbenchmarks for date rendering; equivalence-pinning tests double as behavioral benchmarks.
  • Documentation Update: no user-facing API changes; behavior notes live in code comments.
  • Changelog/What's New: perf — utils v2.4.0 helper adoption, adaptor/proxy allocation cuts; fix — cache Expires RFC 9110 parsing, adaptor multi-value header preservation.
  • Migration Guide: not needed — no breaking changes.

Type of change

  • Performance improvement (non-breaking change which improves efficiency)
  • Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Added or updated unit tests to validate the effectiveness of the changes or new features (RFC 850 Expires caching, OWS-padded dates, encodeQueryValuesurl.Values.Encode, resolveRemoteAddrResolveTCPAddr, zoned-IPv6 rejection, multi-value header preservation — the behavioral tests were verified to fail against the pre-change code).
  • 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 (none added; uses the already-bumped gofiber/utils v2.4.0).
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK

claude added 6 commits July 23, 2026 20:52
Use the helpers introduced in gofiber/utils v2.4.0 to cut allocations
and speed up per-request work:

- req.go: parseHTTPDate now delegates to utils.ParseHTTPDate, which
  keeps net/http.ParseTime semantics (IMF-fixdate plus the obsolete
  RFC 850 and asctime formats) behind an allocation-free fast path,
  removing the string(date) conversion on the fallback.
- req.go: isTrustedProxyIP parses proxy IPs with utils.ParseIPv4 and
  utils.ParseIPv6 instead of netip.ParseAddr, skipping the error
  allocation for invalid client-supplied addresses.
- redirect.go: Route escapes query values straight into the pooled
  buffer with utils.AppendQueryEscape instead of allocating an
  intermediate string via url.QueryEscape.
- middleware/cache: the key generator escapes query keys and values
  in place with utils.AppendQueryEscape, dropping two intermediate
  strings per parameter pair; the buffer bound check is restated
  post-append in an exactly equivalent form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
- middleware/cache: format the Date header with utils.AppendHTTPDate,
  a direct field writer that is ~3.8x faster than fasthttp's
  time.AppendFormat-based AppendHTTPDate (150ns -> 39ns per call),
  used on both the cache-hit and cache-store paths.
- middleware/cache: parse the Date header with utils.ParseHTTPDate.
  The fast path matches fasthttp's for canonical IMF-fixdate input,
  and the fallback now also accepts the obsolete RFC 850 and asctime
  forms that RFC 9110 §5.6.7 requires recipients to handle.
- middleware/paginate: render pagination query strings with a
  url.Values.Encode equivalent that escapes via utils.AppendQueryEscape
  into a single buffer instead of allocating an intermediate string per
  key and value; output is pinned byte-for-byte to Encode by a new test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
net.ParseIP is netip.ParseAddr minus zoned addresses, so screening out
'%' first and delegating to utils.ParseIPv4/ParseIPv6 (new in utils
v2.4.0) keeps acceptance identical while avoiding the net.IP slice
allocation on success and the error allocation on failure:

- middleware/hostauthorization: IPv6-literal Host values are validated
  allocation-free on the per-request syntax check.
- client: the cookie jar's isIPLiteral does the same for cookie domain
  matching.

Both behaviors are pinned by new tests, including zoned-address
rejection and IPv4-mapped IPv6 acceptance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
An adversarial audit of the earlier utils.ParseHTTPDate conversions
surfaced two fidelity gaps:

- middleware/cache parsed the Date header with utils.ParseHTTPDate
  (IMF-fixdate plus the obsolete RFC 850 and asctime forms) while
  Expires still went through fasthttp.ParseHTTPDate (IMF-fixdate
  only), so a response dating itself consistently in RFC 850 got its
  Date honored but its Expires routed to the parse-error
  force-revalidate path. Expires now uses utils.ParseHTTPDate too; a
  new test pins that a future RFC 850 Expires enables caching.
- utils.ParseHTTPDate tolerates surrounding ASCII whitespace, which
  net/http.ParseTime does not — reachable via handler-set dates
  carrying stray whitespace (fasthttp rewrites embedded newlines to
  spaces). This tolerance matches RFC 9110 §5.5's exclusion of
  leading/trailing OWS from field values, so it is kept, documented
  on parseHTTPDate, and pinned by a Fresh() test case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
adaptor (net/http -> fiber path in handlerFunc):
- Build the fasthttp request directly into the pooled RequestCtx instead
  of filling a standalone Request and CopyTo-ing it across. Init2 only
  touches connection metadata and buffer-retention flags, so in-place
  construction is exactly fasthttp's own Init order minus the full
  header+body copy. A fresh-body 1MB request drops ~35% in ns/op and
  four allocations.
- Pool the noopConn together with the RequestCtx and share one stateless
  disabledLogger, removing two per-request allocations.
- resolveRemoteAddr takes a fast path for numeric ip:port literals — the
  only form net/http servers produce — via utils.ParseIPv4/ParseIPv6
  and a strict decimal port parse, skipping net.ResolveTCPAddr's
  resolver machinery; hostnames and service ports still fall back to
  it. A new test pins the fast path to ResolveTCPAddr's results,
  including IPv6 zones and leading-zero forms.

proxy:
- Strip Connection-listed headers by iterating the header value bytes
  in place instead of materializing a string copy and a name slice:
  BenchmarkStripHopByHop_WithConnection goes from 1565ns/72B/3allocs
  to ~958ns with zero allocations. The names alias the value buffers,
  which is sound because fasthttp's Del re-slices the entry list
  without rewriting other entries' buffers. The fuzz harness now
  exercises deletion against a real header.
- followRedirects renders each hop's URL once instead of twice,
  removing a URL.String() allocation per hop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
The header copy loops in handlerFunc and HTTPMiddleware called
Header.Set once per value, so repeated header lines (e.g. two
X-Forwarded-For entries) collapsed to the last value. Set the first
value — keeping replace semantics for keys already present on the
target request — and Add the rest. fasthttp's Add intentionally keeps
singleton semantics for Cookie/Content-Type/etc., so those behave as
before. New tests pin both conversion paths and fail against the old
code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
@gaby
gaby requested a review from a team as a code owner July 24, 2026 13:06
@ReneWerner87 ReneWerner87 added this to v3 Jul 24, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jul 24, 2026
@gaby gaby changed the title perf: adopt gofiber/utils v2.4.0 helpers on hot paths ⚡ perf: adopt gofiber/utils v2.4.0 helpers Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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

The PR standardizes IP and HTTP-date parsing, improves adapter pooling and multi-value header preservation, adds literal remote-address handling, replaces query escaping paths, and rewires proxy connection-header deletion with expanded tests.

Changes

Runtime and protocol updates

Layer / File(s) Summary
IP and HTTP-date parsing
client/cookiejar.go, middleware/hostauthorization/*, req.go, middleware/cache/*, ctx_test.go
Shared utility parsers handle IP literals and HTTP dates, zoned IPv6 is rejected, and obsolete or whitespace-tolerant date formats are tested.
Pooled adapter state and address resolution
middleware/adaptor/adaptor.go, middleware/adaptor/adaptor_test.go
The adapter pools request context and connection state, fast-paths literal IP addresses, and preserves repeated headers across conversions.
Direct query escaping and redirect reuse
middleware/cache/keygen.go, middleware/paginate/*, redirect.go, middleware/proxy/proxy.go
Query values are escaped directly into buffers, pagination encoding is checked against url.Values.Encode, and redirect URL strings are reused per hop.
Connection-listed header deletion
middleware/proxy/security.go, middleware/proxy/fuzz_test.go
Request and response stripping share an in-place deletion helper, with fuzzing against real fasthttp headers.
Deterministic cancellation coverage
client/client_test.go
The cancellation benchmark coordinates handler release through a channel instead of a fixed delay.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTPMiddleware
  participant handlerFunc
  participant resolveRemoteAddr
  participant Fiber
  Client->>HTTPMiddleware: send net/http request with repeated headers
  HTTPMiddleware->>handlerFunc: copy multi-value headers
  handlerFunc->>resolveRemoteAddr: resolve literal or fallback address
  handlerFunc->>Fiber: initialize pooled fasthttp context
  Fiber-->>Client: return converted response
Loading

Possibly related PRs

Suggested labels: 🤖 Dependencies

Suggested reviewers: sixcolors, renewerner87, efectn

Poem

I hop through headers, neat and bright,
And parse the dates by moonlit night.
IPs shed zones, queries gleam,
Pooled contexts flow downstream.
The rabbit stamps this change: “All right!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change set: adopting utils v2.4.0 helpers and optimizing adaptor/proxy hot paths.
Description check ✅ Passed The description follows the template well, covering changes, benchmarks, type of change, and checklist items with enough detail.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/gofiber-utils-helpers-ia74zz

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.

@gaby gaby changed the title ⚡ perf: adopt gofiber/utils v2.4.0 helpers ⚡ perf: adopt gofiber/utils v2.4.0 helpers and optimize adaptor/proxy hot paths Jul 24, 2026
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.28%. Comparing base (6639aef) to head (e66becd).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4557      +/-   ##
==========================================
+ Coverage   93.25%   93.28%   +0.03%     
==========================================
  Files         140      140              
  Lines       14645    14685      +40     
==========================================
+ Hits        13657    13699      +42     
+ Misses        615      614       -1     
+ Partials      373      372       -1     
Flag Coverage Δ
unittests 93.28% <100.00%> (+0.03%) ⬆️

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.

@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 `@middleware/adaptor/adaptor.go`:
- Around line 57-64: Fix the lint failures in pooledCtx and emptyStringTest:
reorder pooledCtx fields according to fieldalignment’s recommended layout, then
update the empty-string assertion in emptyStringTest to use the repository’s
lint-approved empty-string check. Keep behavior unchanged.
🪄 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 Plus

Run ID: 24378246-6c90-4370-8a03-c27d8e06ccc8

📥 Commits

Reviewing files that changed from the base of the PR and between 6639aef and c53d865.

📒 Files selected for processing (18)
  • client/cookiejar.go
  • client/cookiejar_test.go
  • ctx_test.go
  • middleware/adaptor/adaptor.go
  • middleware/adaptor/adaptor_test.go
  • middleware/cache/cache.go
  • middleware/cache/cache_test.go
  • middleware/cache/keygen.go
  • middleware/cache/utils.go
  • middleware/hostauthorization/hostauthorization.go
  • middleware/hostauthorization/hostauthorization_test.go
  • middleware/paginate/page_info.go
  • middleware/paginate/paginate_test.go
  • middleware/proxy/fuzz_test.go
  • middleware/proxy/proxy.go
  • middleware/proxy/security.go
  • redirect.go
  • req.go

Comment thread middleware/adaptor/adaptor.go
@gaby gaby removed the v2 label Jul 24, 2026
- parseDecimalPort: use s == "" (gocritic emptyStringTest).
- pooledCtx: conn field first to minimize the trailing pointer span
  (govet fieldalignment).
- paginate test: rename a local that started shadowing the net/url
  import added for the encodeQueryValues test (gocritic importShadow).
- Cover the previously untested branches codecov flagged: empty and
  oversized and non-numeric port strings in the resolveRemoteAddr
  equivalence test, and empty header value slices in both header copy
  loops.

Verified with golangci-lint v2.12.2 (the CI version): 0 issues.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK

@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)
middleware/adaptor/adaptor_test.go (1)

1896-1914: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the empty header slice is skipped.

The test sets X-Empty to nil but never checks it. A regression that copies an empty header into Fiber would still pass; capture PeekAll("X-Empty") and assert it is empty.

Suggested assertion
 var got []string
+var emptyHeaderValues int
 app.Get("/", func(c fiber.Ctx) error {
 	for _, v := range c.Request().Header.PeekAll("X-Multi") {
 		got = append(got, string(v))
 	}
+	emptyHeaderValues = len(c.Request().Header.PeekAll("X-Empty"))
 	return c.SendStatus(fiber.StatusOK)
 })
 
 require.Equal(t, []string{"one", "two"}, got)
+require.Zero(t, emptyHeaderValues)
🤖 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 `@middleware/adaptor/adaptor_test.go` around lines 1896 - 1914, Update the test
handler in the existing middleware test to capture
c.Request().Header.PeekAll("X-Empty") and assert that the captured values are
empty, alongside the existing X-Multi assertion. Keep the current X-Multi
verification unchanged.
🤖 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 `@middleware/adaptor/adaptor_test.go`:
- Around line 1896-1914: Update the test handler in the existing middleware test
to capture c.Request().Header.PeekAll("X-Empty") and assert that the captured
values are empty, alongside the existing X-Multi assertion. Keep the current
X-Multi verification unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b1e98d3-7291-4148-b078-b60ab1f80e76

📥 Commits

Reviewing files that changed from the base of the PR and between c53d865 and 647a9fb.

📒 Files selected for processing (3)
  • middleware/adaptor/adaptor.go
  • middleware/adaptor/adaptor_test.go
  • middleware/paginate/paginate_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • middleware/paginate/paginate_test.go
  • middleware/adaptor/adaptor.go

Follow-up to a review note: the HTTPMiddleware multi-value test set an
empty X-Empty value slice to cover the skip branch but never verified
the header stays absent on the fiber request. Capture PeekAll("X-Empty")
and assert it is empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK

@github-actions github-actions 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.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 979e23e Previous: 6639aef Ratio
BenchmarkDecoderedirectionMsg (github.com/gofiber/fiber/v3) 175.1 ns/op 182.76 MB/s 0 B/op 0 allocs/op 79.62 ns/op 401.88 MB/s 0 B/op 0 allocs/op 2.20
BenchmarkDecoderedirectionMsg (github.com/gofiber/fiber/v3) - ns/op 175.1 ns/op 79.62 ns/op 2.20

This comment was automatically generated by workflow using github-action-benchmark.

@ReneWerner87 ReneWerner87 removed the v2 label Jul 24, 2026

gaby commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

The benchmark alert on BenchmarkStripHopByHop_NoConnection (1.55× vs a 1.50 threshold) looks like runner noise rather than a real regression — that benchmark exercises the path this PR did not change (no Connection header present, so the new in-place deletion helper does zero work), and both sides report 0 B / 0 allocs.

Interleaved A/B on one machine (-benchtime=300000x -count=3, main 6639aef in a separate worktree vs this branch):

Benchmark main this branch
StripHopByHop_NoConnection ~1121 ns/op, 0 allocs ~1130 ns/op, 0 allocs (±noise)
StripHopByHop_WithConnection ~2650 ns/op, 72 B, 3 allocs ~1546 ns/op, 0 B, 0 allocs

I've queued a re-run of the failed benchmark job.


Generated by Claude Code

gaby commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Benchmark check update: the first re-run cleared the StripHopByHop_NoConnection alert but flagged a different benchmark, BenchmarkDecoderedirectionMsg (msgp-generated decode code this PR doesn't touch), at 2.20× — with MB/s halved proportionally, i.e. the whole benchmark ran on slower hardware. Interleaved local A/B against main 6639aef (-benchtime=2000000x -count=3): ~61.6 ns/op on main vs ~62.8 ns/op on this branch — flat within noise, 0 allocs both.

Two runs flagging two different benchmarks in untouched code, both flat in controlled A/B, points to runner variance against the single-run baseline rather than a regression. I've queued another re-run; if it trips a third unrelated benchmark, I'll leave it for a maintainer's judgment with this data on record.


Generated by Claude Code

Benchmark_Client_Request_Send_ContextCancel gave the handler a fixed
1ms sleep as the window for the benchmark loop to cancel the request
context. On fast CI runners the response wins that race, Send returns
a real response, and the nil assertion fails — as seen on the PR
benchmark job (AMD EPYC runner). Replace the sleep with an explicit
handshake: the handler responds only after the loop has called
cancel(), so the context's Done channel is closed strictly before the
response can be produced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK

gaby commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

The third benchmark-job failure had a different root cause than the two perf-threshold flakes: Benchmark_Client_Request_Send_ContextCancel failed its require.Nil assertion. That benchmark is pre-existing (untouched by this PR) and racy — the handler's fixed 1 ms sleep is the only window for the context cancellation to land, and on the fast AMD EPYC runner the response won the race. Fixed in 51f312f by replacing the sleep with an explicit handshake so the handler can only respond after cancel() has been called. The push re-runs the full suite.


Generated by Claude Code

golangci-lint's misspell rule rejects "cancelled"; rename the
handshake channel and comment to "canceled" to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK

@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 `@client/client_test.go`:
- Line 2405: Update the comment in the benchmark response flow to use the
repository’s “canceled” spelling instead of “cancelled,” without changing the
surrounding behavior or code.
- Around line 2408-2409: Remove the time.Sleep-based ordering in the
cancellation test around cancelledCh. Extend the cancellation handshake so it
acknowledges that the cancellation path has fully drained or released the
request before allowing the response to proceed, then await that acknowledgement
before asserting the response; preserve the existing cancellation behavior and
require.Nil check.
🪄 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 Plus

Run ID: d5506c5c-9938-4083-960a-836081ee09ba

📥 Commits

Reviewing files that changed from the base of the PR and between 979e23e and 51f312f.

📒 Files selected for processing (1)
  • client/client_test.go

Comment thread client/client_test.go Outdated
Comment thread client/client_test.go Outdated
Review follow-up: the previous handshake proved cancel() had been
called before the handler responded, but the client's select could
still in principle see both the Done and response channels ready and
pick randomly. core.go's ctx.Done() branch returns immediately and
hands the in-flight response to a drainer goroutine, so the loop can
receive Send's outcome while the handler is still blocked — at that
point the response channel cannot be ready and the cancellation branch
is the only possible result. The handler is released only afterwards,
and the sleep is gone entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5sm1LHEnvEVDRGsHWFfKK
@ReneWerner87 ReneWerner87 removed the v2 label Jul 24, 2026
@ReneWerner87
ReneWerner87 merged commit 84ecfab into main Jul 24, 2026
20 checks passed
@github-project-automation github-project-automation Bot moved this to Done in v3 Jul 24, 2026
@ReneWerner87
ReneWerner87 deleted the claude/gofiber-utils-helpers-ia74zz branch July 24, 2026 16:24
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