Skip to content

perf(fetch): skip empty ResponseInit work and send buffered bodies directly - #5702

Open
anonrig wants to merge 3 commits into
mainfrom
cursor/fetch-response-init-buffered-body-7afe
Open

perf(fetch): skip empty ResponseInit work and send buffered bodies directly#5702
anonrig wants to merge 3 commits into
mainfrom
cursor/fetch-response-init-buffered-body-7afe

Conversation

@anonrig

@anonrig anonrig commented Aug 18, 2026

Copy link
Copy Markdown
Member

This relates to...

Rationale

#5701 skipped Request constructor work when RequestInit was empty (dictionary defaults were making every fetch(url) look non-empty) and dropped the idle-socket setTimeout(0) floor.

This PR applies the same ideas to the next similar hot paths, independently of that branch:

  1. ResponseInit defaultsstatus: 200 and statusText: "" meant every new Response() / new Response(body) ran WebIDL conversion and reason-phrase validation for values makeResponse() already has.
  2. Buffered request bodiesextractBody() already keeps string / BufferSource bodies on source, but HTTP-network-fetch still teed the companion Web Stream and incrementally read it before handing bytes to the dispatcher.
  3. Idle-socket validation start time — validation was only scheduled when the next request arrived. Starting it when the socket becomes idle lets the GHSA yield overlap body consumption instead of gating the next dispatch.

Changes

  • Treat “init has members” as user-specified ResponseInit fields, not WebIDL defaults. Omitted / {} init skips conversion; makeResponse() already supplies 200 / "".
  • If request.body.source is a string or Uint8Array, clone headers only (no stream tee) and pass source to agent.dispatch.
  • Schedule H1 idle-socket validation in onMessageComplete when kSocketUsed becomes true. The existing write-time clearIdleSocketValidation and poisoning checks are unchanged.

Features

N/A

Bug Fixes

N/A

Breaking Changes and Depreciations

N/A

Benchmarks

Loopback, Node 22.14. Measured against main (e609acb0):

main this PR
new Response() 4.1M ops/s 11.1M ops/s (~2.7×)
new Response(null, { status: 201 }) 3.1M ops/s 2.9M ops/s (unchanged path)
fetch POST string body p50 1.66 ms 1.49 ms
sequential fetch().text() p50 1.48 ms 1.45 ms

The GET keep-alive number is still dominated by setTimeout(0) on main; #5701 replaces that with setImmediate. Starting validation at idle is complementary once that lands.

Status

@anonrig
anonrig requested review from KhafraDev and mcollina August 18, 2026 16:08
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.91667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.47%. Comparing base (f387057) to head (c75d899).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/web/fetch/index.js 92.30% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5702      +/-   ##
==========================================
- Coverage   93.47%   93.47%   -0.01%     
==========================================
  Files         110      110              
  Lines       38846    38929      +83     
==========================================
+ Hits        36310    36387      +77     
- Misses       2536     2542       +6     

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@anonrig
anonrig force-pushed the cursor/fetch-response-init-buffered-body-7afe branch 2 times, most recently from f550e4a to fc09325 Compare August 18, 2026 18:06
…rectly

ResponseInit defaults status/statusText, so every new Response() ran
WebIDL conversion and reason-phrase validation for values makeResponse()
already has.

extractBody() already keeps string/BufferSource bodies on `source`, but
http-network-fetch still teed the companion Web Stream and incrementally
read it. Send those bytes to the dispatcher instead.

Start H1 idle-socket validation when the socket becomes idle so the
GHSA-35p6-xmwp-9g52 event-loop yield overlaps body consumption instead
of gating the next dispatch.

Assisted by Cursor
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Arming idle-socket validation on message-complete set
kIdleSocketValidation to 1, so Client.busy() was true while no
request was waiting. BalancedPool then skipped that upstream and
weighted-round-robin ratios drifted (CI cases 5, 6, 9).

Start the poll-yield timer in the background and only block the
socket once a request is actually queued.

Assisted by Cursor
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Codecov flagged 11 untested lines in the string/Uint8Array dispatch
fast path — fetch() never sets processRequestEndOfBody. Exercise both
source types through fetching() and send a Uint8Array POST through
the public API.

Assisted by Cursor
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig
anonrig force-pushed the cursor/fetch-response-init-buffered-body-7afe branch from fc09325 to c75d899 Compare August 18, 2026 18:45

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you please split the changes to client-h1 from the fetch ones?

// Block further writes on this socket until the poll yield completes.
socket[kIdleSocketValidation] = 1
armIdleSocketValidation(client, socket)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you send the changes to this as a separate PR?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants