Skip to content

Conversation

ardatan
Copy link
Owner

@ardatan ardatan commented Jul 25, 2025

Inherits and closes #2638

Fixes the overhead of pipeline function so it catches the performance of Feb 2025 version.

Older version performance ardatan/feTS#2915
Regression ardatan/feTS#2918

~9% increase in perf benchmarking Hive Gateway, memory consumption feels lower too
~20% increase in perf benchmarking feTS

Copy link
Contributor

coderabbitai bot commented Jul 25, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of data streams to enhance reliability and prevent potential hangs during streaming operations.
    • Adjusted error and abort handling for streamed responses to provide more robust cleanup and error propagation.
  • Tests

    • Updated a test to check for the presence of a response rather than exact content, ensuring broader compatibility with streaming changes.

Walkthrough

This set of changes refactors the stream handling logic in the @whatwg-node/node-fetch package and related utilities. It replaces the previous promise-based pipeline and custom passthrough wrappers with direct stream piping and a new pipeThrough utility. Associated types, error handling, and test assertions are updated to reflect these changes.

Changes

Files/Paths Change Summary
.changeset/chatty-cats-pull.md Documents the patch, summarizing the move away from promise pipelines to direct stream piping.
packages/node-fetch/src/Body.ts Removes IncomingMessage handling, simplifies body initialization, updates signal assignment, and adjusts chunk collection logic.
packages/node-fetch/src/fetchCurl.ts Removes custom passthrough logic, directly pipes streams using PassThrough.
packages/node-fetch/src/fetchNodeHttp.ts Removes passthrough utility, uses new pipeThrough for explicit piping and error handling.
packages/node-fetch/src/utils.ts Replaces wrapIncomingMessageWithPassthrough with pipeThrough, adds AbortError, improves error and abort management.
packages/server/test/reproductions.spec.ts Relaxes a test assertion from strict equality to a defined check for stream text content.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant fetchNodeHttp
    participant nodeResponse
    participant outputStream

    Client->>fetchNodeHttp: Initiate fetch
    fetchNodeHttp->>nodeResponse: Receive HTTP response
    fetchNodeHttp->>outputStream: Create or use output stream
    fetchNodeHttp->>outputStream: pipeThrough({ src: nodeResponse, dest: outputStream })
    outputStream-->>fetchNodeHttp: Emits data or error events
    fetchNodeHttp-->>Client: Returns response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Suggested reviewers

  • enisdenjo

Poem

🐇
Streams now flow with less ado,
No promise chains to muddle through.
Pipe it here and pipe it there,
With errors handled with extra care.
The code is lean, the tests are tight—
This bunny hops with pure delight!
🌊✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch avoid-pipeline-promise

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ardatan ardatan force-pushed the avoid-pipeline-promise branch from f9e7821 to a591646 Compare July 25, 2025 16:21
Copy link
Contributor

github-actions bot commented Jul 25, 2025

@benchmarks/node-fetch results (consumeBody)

   ✓ active_handles.................: avg=140.793492 min=31      med=141     max=185      p(90)=159     p(95)=162    
     data_received..................: 22 MB  737 kB/s
     data_sent......................: 14 MB  472 kB/s
     http_req_blocked...............: avg=3.21µs     min=661ns   med=1.55µs  max=4.89ms   p(90)=2.1µs   p(95)=2.46µs 
     http_req_connecting............: avg=1.18µs     min=0s      med=0s      max=4.78ms   p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=20.68ms    min=3.68ms  med=20.23ms max=897.18ms p(90)=26.37ms p(95)=27.8ms 
       { expected_response:true }...: avg=20.68ms    min=3.68ms  med=20.23ms max=897.18ms p(90)=26.37ms p(95)=27.8ms 
     http_req_failed................: 0.00%  ✓ 0           ✗ 144509
     http_req_receiving.............: avg=37.87µs    min=10.14µs med=27.1µs  max=15.94ms  p(90)=42.18µs p(95)=50.19µs
     http_req_sending...............: avg=12.28µs    min=3.55µs  med=7.91µs  max=18.37ms  p(90)=10.81µs p(95)=15.62µs
     http_req_tls_handshaking.......: avg=0s         min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=20.63ms    min=3.65ms  med=20.18ms max=897.08ms p(90)=26.33ms p(95)=27.72ms
     http_reqs......................: 144509 4816.531544/s
     iteration_duration.............: avg=41.48ms    min=15.15ms med=40.23ms max=917.89ms p(90)=45.11ms p(95)=49.95ms
     iterations.....................: 72244  2407.915804/s
     vus............................: 100    min=100       max=100 
     vus_max........................: 100    min=100       max=100 

Copy link
Contributor

github-actions bot commented Jul 25, 2025

@benchmarks/node-fetch results (noConsumeBody)

   ✓ active_handles.................: avg=139.727626 min=35      med=139     max=186      p(90)=163     p(95)=167    
     data_received..................: 24 MB  783 kB/s
     data_sent......................: 15 MB  507 kB/s
     http_req_blocked...............: avg=3.5µs      min=621ns   med=1.56µs  max=6.6ms    p(90)=2.13µs  p(95)=2.49µs 
     http_req_connecting............: avg=1.44µs     min=0s      med=0s      max=4.53ms   p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=19.47ms    min=2.63ms  med=18.96ms max=829.16ms p(90)=25.38ms p(95)=26.94ms
       { expected_response:true }...: avg=19.47ms    min=2.63ms  med=18.96ms max=829.16ms p(90)=25.38ms p(95)=26.94ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 153472
     http_req_receiving.............: avg=35.81µs    min=8.7µs   med=25.79µs max=20.15ms  p(90)=40.39µs p(95)=48.37µs
     http_req_sending...............: avg=12.07µs    min=3.09µs  med=7.84µs  max=23.6ms   p(90)=10.82µs p(95)=16.11µs
     http_req_tls_handshaking.......: avg=0s         min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=19.42ms    min=2.48ms  med=18.91ms max=829.08ms p(90)=25.33ms p(95)=26.86ms
     http_reqs......................: 153472 5115.167399/s
     iteration_duration.............: avg=39.06ms    min=11.09ms med=37.79ms max=845.89ms p(90)=42.92ms p(95)=47.72ms
     iterations.....................: 76718  2556.983766/s
     vus............................: 100    min=100       max=100 
     vus_max........................: 100    min=100       max=100 

Copy link
Contributor

github-actions bot commented Jul 25, 2025

@benchmarks/server results (ponyfill)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 246392      ✗ 0     
     data_received..................: 24 MB   809 kB/s
     data_sent......................: 18 MB   612 kB/s
     http_req_blocked...............: avg=1.55µs   min=852ns    med=1.46µs   max=159.49µs p(90)=1.98µs   p(95)=2.15µs  
     http_req_connecting............: avg=0ns      min=0s       med=0s       max=106.09µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=168.52µs min=115.72µs med=162.13µs max=5.79ms   p(90)=183.21µs p(95)=190.93µs
       { expected_response:true }...: avg=168.52µs min=115.72µs med=162.13µs max=5.79ms   p(90)=183.21µs p(95)=190.93µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 123196
     http_req_receiving.............: avg=24.57µs  min=13.19µs  med=23.29µs  max=2.97ms   p(90)=28.13µs  p(95)=31µs    
     http_req_sending...............: avg=9.07µs   min=5.55µs   med=9.21µs   max=279.28µs p(90)=11.41µs  p(95)=13.04µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=134.87µs min=90.86µs  med=129.03µs max=5.68ms   p(90)=148.2µs  p(95)=155.46µs
     http_reqs......................: 123196  4106.388841/s
     iteration_duration.............: avg=238.97µs min=172.65µs med=231.92µs max=6.29ms   p(90)=255.86µs p(95)=265.89µs
     iterations.....................: 123196  4106.388841/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

Copy link
Contributor

github-actions bot commented Jul 25, 2025

@benchmarks/server results (undici)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 165440      ✗ 0    
     data_received..................: 17 MB   549 kB/s
     data_sent......................: 12 MB   411 kB/s
     http_req_blocked...............: avg=1.69µs   min=912ns    med=1.65µs   max=169.28µs p(90)=2.09µs   p(95)=2.38µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=122.14µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=283.78µs min=209.46µs med=261.68µs max=16.28ms  p(90)=297.59µs p(95)=316.92µs
       { expected_response:true }...: avg=283.78µs min=209.46µs med=261.68µs max=16.28ms  p(90)=297.59µs p(95)=316.92µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 82720
     http_req_receiving.............: avg=27.41µs  min=15.45µs  med=26.07µs  max=2.74ms   p(90)=32.23µs  p(95)=35.63µs 
     http_req_sending...............: avg=9.84µs   min=5.72µs   med=9.74µs   max=307.69µs p(90)=12.32µs  p(95)=14.62µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=246.52µs min=182.68µs med=225.35µs max=16.19ms  p(90)=258.77µs p(95)=276.92µs
     http_reqs......................: 82720   2757.227261/s
     iteration_duration.............: avg=357.79µs min=270.64µs med=334.38µs max=16.49ms  p(90)=374.24µs p(95)=398.28µs
     iterations.....................: 82720   2757.227261/s
     vus............................: 1       min=1         max=1  
     vus_max........................: 1       min=1         max=1  

Copy link
Contributor

github-actions bot commented Jul 25, 2025

@benchmarks/server results (native)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 175812      ✗ 0    
     data_received..................: 18 MB   583 kB/s
     data_sent......................: 13 MB   437 kB/s
     http_req_blocked...............: avg=1.64µs   min=922ns    med=1.58µs   max=3.89ms   p(90)=1.95µs   p(95)=2.12µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=122.82µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=266.27µs min=197.36µs med=247.44µs max=14.83ms  p(90)=279.95µs p(95)=295.52µs
       { expected_response:true }...: avg=266.27µs min=197.36µs med=247.44µs max=14.83ms  p(90)=279.95µs p(95)=295.52µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 87906
     http_req_receiving.............: avg=26.03µs  min=14.78µs  med=24.73µs  max=2.58ms   p(90)=30.31µs  p(95)=33.36µs 
     http_req_sending...............: avg=9.42µs   min=5.61µs   med=9.49µs   max=299.52µs p(90)=11.36µs  p(95)=13.1µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=230.81µs min=169.9µs  med=212.92µs max=14.76ms  p(90)=243.09µs p(95)=257.69µs
     http_reqs......................: 87906   2930.099387/s
     iteration_duration.............: avg=336.75µs min=256.78µs med=316.72µs max=14.97ms  p(90)=352.61µs p(95)=371.82µs
     iterations.....................: 87906   2930.099387/s
     vus............................: 1       min=1         max=1  
     vus_max........................: 1       min=1         max=1  

Copy link
Contributor

github-actions bot commented Jul 26, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@whatwg-node/node-fetch 0.7.23-alpha-20250727165811-6c05e293622755103fa748bf8ebf677c55ed0143 npm ↗︎ unpkg ↗︎

@ardatan ardatan force-pushed the avoid-pipeline-promise branch 2 times, most recently from 683ac7e to 27dea3e Compare July 26, 2025 08:58
@ardatan ardatan force-pushed the avoid-pipeline-promise branch from f87c506 to 6c05e29 Compare July 27, 2025 16:56
@ardatan ardatan marked this pull request as ready for review July 27, 2025 16:56
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: 1

🧹 Nitpick comments (1)
packages/node-fetch/src/utils.ts (1)

79-97: Consider potential race condition in abort handling

The abort handler uses WeakRef which could theoretically be garbage collected between the check and the call. While unlikely in practice, consider storing a strong reference during the abort handler execution.

 function onAbort() {
-  srcRef.deref()?.destroy(new AbortError());
+  const src = srcRef.deref();
+  if (src) {
+    src.destroy(new AbortError());
+  }
   cleanup();
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90e4c34 and 6c05e29.

📒 Files selected for processing (6)
  • .changeset/chatty-cats-pull.md (1 hunks)
  • packages/node-fetch/src/Body.ts (4 hunks)
  • packages/node-fetch/src/fetchCurl.ts (2 hunks)
  • packages/node-fetch/src/fetchNodeHttp.ts (3 hunks)
  • packages/node-fetch/src/utils.ts (3 hunks)
  • packages/server/test/reproductions.spec.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:36.028Z
Learning: The node-fetch package tries different fetch implementations in the following order: 1) node-libcurl 2) undici 3) built-in undici in Node 4) node:http as fallback
Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:47.290Z
Learning: The `handleNodeRequestAndResponse` method in `@whatwg-node/server` can be used to integrate with Fastify by passing both the request and reply objects in the route handler, along with the context containing `req` and `reply`.
.changeset/chatty-cats-pull.md (2)

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:36.028Z
Learning: The node-fetch package tries different fetch implementations in the following order: 1) node-libcurl 2) undici 3) built-in undici in Node 4) node:http as fallback

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:47.290Z
Learning: The handleNodeRequestAndResponse method in @whatwg-node/server can be used to integrate with Fastify by passing both the request and reply objects in the route handler, along with the context containing req and reply.

packages/node-fetch/src/fetchCurl.ts (2)

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:36.028Z
Learning: The node-fetch package tries different fetch implementations in the following order: 1) node-libcurl 2) undici 3) built-in undici in Node 4) node:http as fallback

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:47.290Z
Learning: The handleNodeRequestAndResponse method in @whatwg-node/server can be used to integrate with Fastify by passing both the request and reply objects in the route handler, along with the context containing req and reply.

packages/node-fetch/src/fetchNodeHttp.ts (2)

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:36.028Z
Learning: The node-fetch package tries different fetch implementations in the following order: 1) node-libcurl 2) undici 3) built-in undici in Node 4) node:http as fallback

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:47.290Z
Learning: The handleNodeRequestAndResponse method in @whatwg-node/server can be used to integrate with Fastify by passing both the request and reply objects in the route handler, along with the context containing req and reply.

packages/node-fetch/src/Body.ts (1)

Learnt from: ardatan
PR: ardatan/whatwg-node#0
File: :0-0
Timestamp: 2025-01-29T19:45:47.290Z
Learning: The handleNodeRequestAndResponse method in @whatwg-node/server can be used to integrate with Fastify by passing both the request and reply objects in the route handler, along with the context containing req and reply.

🧬 Code Graph Analysis (2)
packages/node-fetch/src/utils.ts (1)
packages/node-fetch/src/Request.ts (1)
  • signal (113-116)
packages/node-fetch/src/Body.ts (2)
packages/node-fetch/src/ReadableStream.ts (1)
  • PonyfillReadableStream (61-273)
packages/promise-helpers/src/index.ts (1)
  • fakePromise (47-94)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: e2e / azure-function
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 18
  • GitHub Check: unit / node 24
  • GitHub Check: unit / node 20
  • GitHub Check: unit / deno
  • GitHub Check: alpha / snapshot
  • GitHub Check: server (native)
  • GitHub Check: server (undici)
  • GitHub Check: server (ponyfill)
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: node-fetch (noConsumeBody)
🔇 Additional comments (11)
packages/server/test/reproductions.spec.ts (1)

80-80: Why was the test assertion relaxed from exact content to just defined check?

The test previously verified that req.text() returns the exact body content 'hello world' when called outside the handler. Now it only checks if the result is defined. This reduces test coverage as it no longer validates that the body content is preserved correctly.

Is this change intentional due to the new stream handling implementation, or should the exact content still be accessible?

.changeset/chatty-cats-pull.md (1)

1-5: LGTM!

The changeset correctly documents this as a patch and clearly describes the change.

packages/node-fetch/src/fetchNodeHttp.ts (2)

45-49: Good simplification of signal assignment

The removal of the redundant else branch makes the code cleaner while maintaining the same functionality.


117-132: Excellent error handling implementation

The use of pipeThrough with proper error handling that destroys both streams on error is a robust approach. The error callback ensures both nodeResponse and outputStream are properly cleaned up.

packages/node-fetch/src/utils.ts (2)

52-100: Well-implemented stream piping utility

The pipeThrough function is a robust replacement for the previous wrapper approach:

  • Proper error propagation from source to destination
  • Comprehensive abort signal handling with cleanup
  • Good use of WeakRef to avoid memory leaks
  • Clear comments explaining the behavior

114-120: Good implementation of AbortError

The AbortError class correctly follows Node.js conventions with appropriate name and message defaults.

packages/node-fetch/src/Body.ts (5)

10-10: Import changes look good.

The addition of fakePromise and isArrayBufferView imports is appropriate as both utilities are used throughout the file.


59-65: Good refactoring of signal handling.

Separating signal assignment from body processing improves code organization and makes the responsibilities clearer.


73-73: Type consistency improvement.

Using undefined instead of null for optional properties aligns with TypeScript best practices.


178-180: Good defensive check for destroyed streams.

This prevents potential errors when attempting to read from destroyed streams and maintains consistency by returning an empty array.


455-461: Function signature simplified appropriately.

Removing the unused signal parameter from processBodyInit aligns with the single responsibility principle and matches the refactored constructor logic.

@ardatan ardatan requested a review from enisdenjo July 27, 2025 17:00
@ardatan ardatan merged commit 7a44c69 into master Jul 28, 2025
25 checks passed
@ardatan ardatan deleted the avoid-pipeline-promise branch July 28, 2025 09:49
@ardatan ardatan mentioned this pull request Jul 28, 2025
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.

2 participants