Skip to content

fix(server): do not hang when server adapter's request is leaked and attempted to parse outside#2208

Merged
ardatan merged 36 commits intomasterfrom
fix-req-leak
Mar 25, 2025
Merged

fix(server): do not hang when server adapter's request is leaked and attempted to parse outside#2208
ardatan merged 36 commits intomasterfrom
fix-req-leak

Conversation

@ardatan
Copy link
Copy Markdown
Owner

@ardatan ardatan commented Mar 21, 2025

When any Request method is called outside server adapter scope, it used to hang.
This PR prevents it to hang and throw an error if the readable stream is destroyed earlier.

let request: Request;
const adapter = createServerAdapter(req => {
    request = req;
  return new Response('Hello World');
});

await request.text(); // Was hanging but now throws an error

Closes #1229

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Resolved an issue where certain network requests would hang indefinitely by ensuring proper error handling when data streams are prematurely terminated.
  • New Features

    • Introduced support for abort signals in HTTP operations, offering improved control over request and response behaviors.
  • Refactor

    • Streamlined server request handling and route processing for enhanced stability and performance.

Walkthrough

This update patches an issue in the @whatwg-node/node-fetch package where calling a Request method on a destroyed stream previously led to an indefinite hang. It introduces error throwing when the stream is prematurely destroyed. Additionally, the changes enhance body handling by adding support for an AbortSignal in the PonyfillBody class and refining chunk collection logic for streams. Several test suites were updated to reflect these behavioral changes, the handling of buffered requests in fetchNodeHttp was streamlined, and Bun compatibility warnings were removed from the server utilities.

Changes

File(s) Change Summary
.changeset/heavy-worms-battle.md Updates documentation for the patch that throws an error on calling methods on a destroyed stream to prevent indefinite hangs.
packages/node-fetch/src/Body.ts Enhances PonyfillBody by adding an optional signal (AbortSignal) to options, refines IncomingMessage handling with a PassThrough stream, and restructures chunk collection using a new helper method.
packages/node-fetch/src/fetchNodeHttp.ts Refactors request body handling by checking for a _buffer on the fetchRequest for direct writing, updates the type of outputStream, and conditionally uses the pipeline based on its availability.
packages/node-fetch/src/ReadableStream.ts Adds [Symbol.toStringTag] property to PonyfillReadableStream for better type identification.
packages/node-fetch/src/Response.ts Adds [Symbol.toStringTag] property to PonyfillResponse for improved object type identification.
packages/server/src/utils.ts Removes the Bun Node compatibility mode warning and associated code from the normalizeNodeRequest function.
packages/server/test/reproductions.spec.ts Replaces an afterAll hook with afterEach to close servers post-test, and adds a conditional test case validating req.text() behavior outside the handler along with introducing createDeferredPromise.
packages/server/test/test-fetch.ts Modifies the test execution condition to run Ponyfill tests if globalThis.Deno is undefined, irrespective of the state of globalThis.Bun.
packages/server/test/abort.spec.ts Introduces a skipIf function to conditionally skip tests based on a boolean condition.
packages/server/test/test-server.ts Modifies the serverImplMap and the runTestsForEachServerImpl function to adjust server implementation registration and name retrieval logic.
packages/server/test/compression.spec.ts Updates runTestsForEachServerImpl to include a second parameter for conditional test skipping based on server implementation.
packages/node-fetch/tests/fetch.spec.ts Increases assertion count and adjusts iteration logic for streamed response tests, ensuring correct abortion behavior after 4 iterations.
packages/server/test/node.spec.ts Implements conditional skipping for test cases based on server implementation (Bun or Deno).

Possibly related PRs

  • Upcoming Release Changes #2032: The changes in the main PR, which address the handling of request methods in the @whatwg-node/node-fetch package, are related to the modifications in the retrieved PR that also involve the @whatwg-node/node-fetch package, specifically regarding the use of native AbortSignal for managing request cancellations.
  • fix(node-fetch/http2): handle Http2 streams correctly #2093: The changes in the main PR, which address the handling of request methods in the @whatwg-node/node-fetch package, are related to the modifications in the retrieved PR that also focus on fixing request handling issues within the same package, specifically for HTTP/2 streams.
  • Upcoming Release Changes #2081: The changes in the main PR, which address a bug in the @whatwg-node/node-fetch package related to request handling, are related to the retrieved PR that also includes a patch for the same package, fixing a similar issue with set-cookies in the HeadersInit interface.

Suggested reviewers

  • dotansimha
  • enisdenjo
  • EmrysMyrddin

Poem

I'm a rabbit with hops so light,
Skipping through updates in the night,
No more hangs, errors now arise,
With AbortSignals to our surprise!
Carrots and code in a joyful delight 🥕🐇
Hop, skip, and debug — everything's right!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @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 requested a review from enisdenjo March 21, 2025 15:45
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 21, 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.15-alpha-20250324224943-fe2e257e455b2c5d9f69fb4b30dd1be84d2217e1 npm ↗︎ unpkg ↗︎
@whatwg-node/server 0.10.3-alpha-20250324224943-fe2e257e455b2c5d9f69fb4b30dd1be84d2217e1 npm ↗︎ unpkg ↗︎

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 21, 2025

@benchmarks/node-fetch results (noConsumeBody)

   ✓ active_handles.................: avg=139.724058 min=12       med=139     max=192      p(90)=161     p(95)=167    
     data_received..................: 24 MB  806 kB/s
     data_sent......................: 16 MB  522 kB/s
     http_req_blocked...............: avg=2.37µs     min=611ns    med=1.48µs  max=5.88ms   p(90)=2.09µs  p(95)=2.47µs 
     http_req_connecting............: avg=387ns      min=0s       med=0s      max=2.59ms   p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=18.91ms    min=996.41µs med=18.4ms  max=858.26ms p(90)=24.22ms p(95)=25.63ms
       { expected_response:true }...: avg=18.91ms    min=996.41µs med=18.4ms  max=858.26ms p(90)=24.22ms p(95)=25.63ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 158012
     http_req_receiving.............: avg=35.16µs    min=8.5µs    med=24.81µs max=25.04ms  p(90)=40.29µs p(95)=48.92µs
     http_req_sending...............: avg=12.43µs    min=3µs      med=7.24µs  max=13.42ms  p(90)=10.64µs p(95)=16.46µs
     http_req_tls_handshaking.......: avg=0s         min=0s       med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=18.86ms    min=754.25µs med=18.35ms max=858.19ms p(90)=24.17ms p(95)=25.56ms
     http_reqs......................: 158012 5266.382031/s
     iteration_duration.............: avg=37.94ms    min=6.37ms   med=36.88ms max=881.95ms p(90)=41.6ms  p(95)=46.81ms
     iterations.....................: 78995  2632.824397/s
     vus............................: 99     min=99        max=100 
     vus_max........................: 100    min=100       max=100 

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 21, 2025

@benchmarks/server results (native)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 232502      ✗ 0     
     data_received..................: 23 MB   779 kB/s
     data_sent......................: 9.3 MB  310 kB/s
     http_req_blocked...............: avg=1.4µs    min=892ns    med=1.19µs   max=166.85µs p(90)=1.89µs   p(95)=2.04µs  
     http_req_connecting............: avg=0ns      min=0s       med=0s       max=114.75µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=196.31µs min=143.71µs med=186.27µs max=7.35ms   p(90)=212.25µs p(95)=221.74µs
       { expected_response:true }...: avg=196.31µs min=143.71µs med=186.27µs max=7.35ms   p(90)=212.25µs p(95)=221.74µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 116251
     http_req_receiving.............: avg=25.09µs  min=13.5µs   med=23.35µs  max=2.28ms   p(90)=30.6µs   p(95)=33.37µs 
     http_req_sending...............: avg=6.18µs   min=3.98µs   med=5.28µs   max=279.63µs p(90)=8.08µs   p(95)=8.58µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=165.04µs min=118.28µs med=154.37µs max=7.32ms   p(90)=177.63µs p(95)=186.46µs
     http_reqs......................: 116251  3874.912103/s
     iteration_duration.............: avg=253.69µs min=185.31µs med=242.78µs max=7.43ms   p(90)=272.02µs p(95)=284.2µs 
     iterations.....................: 116251  3874.912103/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 21, 2025

@benchmarks/server results (undici)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 213844      ✗ 0     
     data_received..................: 22 MB   716 kB/s
     data_sent......................: 8.6 MB  285 kB/s
     http_req_blocked...............: avg=1.41µs   min=882ns    med=1.2µs    max=275.28µs p(90)=1.89µs   p(95)=2.06µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=122.14µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=217.24µs min=158.46µs med=204.99µs max=64.37ms  p(90)=231.53µs p(95)=240.73µs
       { expected_response:true }...: avg=217.24µs min=158.46µs med=204.99µs max=64.37ms  p(90)=231.53µs p(95)=240.73µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 106922
     http_req_receiving.............: avg=25.68µs  min=13.57µs  med=23.94µs  max=3.05ms   p(90)=31.11µs  p(95)=33.74µs 
     http_req_sending...............: avg=6.45µs   min=4.04µs   med=5.67µs   max=299.57µs p(90)=8.26µs   p(95)=9.04µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=185.1µs  min=131.33µs med=173.16µs max=64.28ms  p(90)=196.3µs  p(95)=204.79µs
     http_reqs......................: 106922  3563.953365/s
     iteration_duration.............: avg=276.04µs min=202.23µs med=262.87µs max=64.54ms  p(90)=293.03µs p(95)=304.76µs
     iterations.....................: 106922  3563.953365/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 21, 2025

@benchmarks/node-fetch results (consumeBody)

   ✓ active_handles.................: avg=139.920863 min=13      med=139     max=185      p(90)=162     p(95)=168    
     data_received..................: 23 MB  760 kB/s
     data_sent......................: 15 MB  487 kB/s
     http_req_blocked...............: avg=2.07µs     min=611ns   med=1.46µs  max=4.09ms   p(90)=2.08µs  p(95)=2.41µs 
     http_req_connecting............: avg=73ns       min=0s      med=0s      max=672.85µs p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=20.04ms    min=1.76ms  med=19.44ms max=912.8ms  p(90)=25.37ms p(95)=26.93ms
       { expected_response:true }...: avg=20.04ms    min=1.76ms  med=19.44ms max=912.8ms  p(90)=25.37ms p(95)=26.93ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 149113
     http_req_receiving.............: avg=36.82µs    min=10.28µs med=25.46µs max=14.94ms  p(90)=40.71µs p(95)=48.35µs
     http_req_sending...............: avg=12.27µs    min=3.39µs  med=7.47µs  max=21.22ms  p(90)=10.57µs p(95)=15.29µs
     http_req_tls_handshaking.......: avg=0s         min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=19.99ms    min=1.73ms  med=19.39ms max=912.68ms p(90)=25.32ms p(95)=26.86ms
     http_reqs......................: 149113 4969.705851/s
     iteration_duration.............: avg=40.21ms    min=9.88ms  med=38.91ms max=937ms    p(90)=44.39ms p(95)=49.58ms
     iterations.....................: 74529  2483.936393/s
     vus............................: 100    min=100       max=100 
     vus_max........................: 100    min=100       max=100 

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 21, 2025

@benchmarks/server results (ponyfill)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 279516      ✗ 0     
     data_received..................: 28 MB   918 kB/s
     data_sent......................: 11 MB   373 kB/s
     http_req_blocked...............: avg=1.42µs   min=881ns    med=1.18µs   max=211.66µs p(90)=1.9µs    p(95)=2.07µs  
     http_req_connecting............: avg=0ns      min=0s       med=0s       max=138.34µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=150.97µs min=98.61µs  med=146µs    max=5.88ms   p(90)=167.22µs p(95)=174.74µs
       { expected_response:true }...: avg=150.97µs min=98.61µs  med=146µs    max=5.88ms   p(90)=167.22µs p(95)=174.74µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 139758
     http_req_receiving.............: avg=24.53µs  min=12.39µs  med=23.4µs   max=2.84ms   p(90)=29.53µs  p(95)=32.3µs  
     http_req_sending...............: avg=6.54µs   min=4.15µs   med=5.75µs   max=336.36µs p(90)=8.27µs   p(95)=9.22µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=119.89µs min=72.28µs  med=114.84µs max=5.65ms   p(90)=133.41µs p(95)=139.87µs
     http_reqs......................: 139758  4658.450026/s
     iteration_duration.............: avg=210.07µs min=149.15µs med=204.45µs max=6.15ms   p(90)=229.13µs p(95)=238.64µs
     iterations.....................: 139758  4658.450026/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

Copy link
Copy Markdown
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)
.changeset/heavy-worms-battle.md (1)

5-6: Fix the grammatical issue in the description.

The sentence structure is incorrect. The verb "prevent" should be followed by "from" + gerund, not "to" + verb.

-This PR prevents it to hang and throw an error if the readable stream is destroyed earlier.
+This PR prevents it from hanging and throws an error if the readable stream is destroyed earlier.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~6-~6: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 892ed30 and ed61b3d.

📒 Files selected for processing (3)
  • .changeset/heavy-worms-battle.md (1 hunks)
  • packages/node-fetch/src/Body.ts (2 hunks)
  • packages/server/test/reproductions.spec.ts (2 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
packages/server/test/reproductions.spec.ts (1)
packages/server/src/utils.ts (1) (1)
  • createDeferredPromise (13-13)
packages/node-fetch/src/Body.ts (1)
packages/promise-helpers/src/index.ts (2) (2)
  • fakePromise (47-94)
  • fakeRejectPromise (166-202)
🪛 LanguageTool
.changeset/heavy-worms-battle.md

[uncategorized] ~6-~6: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: unit / deno
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: e2e / azure-function
🔇 Additional comments (7)
.changeset/heavy-worms-battle.md (1)

8-15: Example demonstrates the fix correctly.

Good example showing how the behavior has changed from hanging to throwing an error when a request method is called outside the server adapter scope.

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

5-5: Import of fakeRejectPromise is correctly added.

The import of fakeRejectPromise is necessary for the fix and aligns with the existing architecture.


171-171: Important initialization added.

Initializing this._chunks to an empty array at the beginning of the method ensures consistent behavior regardless of the code path taken.


175-179:

Details

✅ Verification successful

Critical fix for the hanging issue.

This check is the core of the fix - detecting when a readable stream has been destroyed and throwing an appropriate error instead of hanging indefinitely. The error message clearly indicates what happened to help with debugging.


🏁 Script executed:

#!/bin/bash
# Let's verify if this error message is consistent with other similar checks in the codebase

rg -A 2 -B 2 "stream has (already been consumed|been destroyed)" --type ts --no-filename

Length of output: 421


Critical fix verified: Readable stream handling is consistent.

The error message in the code segment (lines 175-179 in packages/node-fetch/src/Body.ts) correctly detects when a readable stream has already been destroyed or consumed, and it provides a clear, consistent error message. The verification using rg confirms that this approach aligns with similar checks elsewhere in the codebase, ensuring proper rejection to avoid hanging issues.

packages/server/test/reproductions.spec.ts (3)

54-81: Good test for validating the fix.

This test case properly validates that the fix works as expected by verifying that req.text() throws the appropriate error message when called outside the server adapter scope. The test follows the same pattern as the example in the changeset.

A few observations:

  1. The test is conditionally included based on environment (excluding Bun and Deno)
  2. It uses modern JavaScript features like await using for resource management
  3. It correctly sets up a minimal reproduction case

56-56: Properly utilizing createDeferredPromise.

The deferred promise pattern is appropriate here to capture the request object outside of the handler scope.


75-79: Proper error handling and assertion.

The test correctly verifies that the expected error is thrown with the appropriate message, validating the fix implemented in Body.ts.

Copy link
Copy Markdown
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)
packages/node-fetch/src/fetchNodeHttp.ts (1)

128-146: Enhanced body handling with buffer optimization.

This change introduces a performance optimization by directly writing the buffer to the request when available, instead of always creating a readable stream. This is a good improvement that simplifies the request sending process in many cases.

For better readability, consider extracting the body handling logic into separate helper functions:

-      if (fetchRequest['_buffer'] != null) {
-        nodeRequest.write(fetchRequest['_buffer']);
-        // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
-        nodeRequest.end(null, null, null);
-      } else {
-        const nodeReadable = (
-          fetchRequest.body != null
-            ? isNodeReadable(fetchRequest.body)
-              ? fetchRequest.body
-              : Readable.from(fetchRequest.body)
-            : null
-        ) as Readable | null;
-        if (nodeReadable) {
-          nodeReadable.pipe(nodeRequest);
-        } else {
-          // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
-          nodeRequest.end(null, null, null);
-        }
-      }
+      sendRequestBody(fetchRequest, nodeRequest);

And define the function at the bottom of the file:

function sendRequestBody(fetchRequest: PonyfillRequest<any>, nodeRequest: any) {
  if (fetchRequest['_buffer'] != null) {
    nodeRequest.write(fetchRequest['_buffer']);
    // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
    nodeRequest.end(null, null, null);
    return;
  }
  
  const nodeReadable = (
    fetchRequest.body != null
      ? isNodeReadable(fetchRequest.body)
        ? fetchRequest.body
        : Readable.from(fetchRequest.body)
      : null
  ) as Readable | null;
  
  if (nodeReadable) {
    nodeReadable.pipe(nodeRequest);
  } else {
    // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
    nodeRequest.end(null, null, null);
  }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ed61b3d and cc31205.

📒 Files selected for processing (3)
  • packages/node-fetch/src/Body.ts (1 hunks)
  • packages/node-fetch/src/fetchNodeHttp.ts (1 hunks)
  • packages/server/test/reproductions.spec.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/test/reproductions.spec.ts
🧰 Additional context used
🧬 Code Definitions (1)
packages/node-fetch/src/fetchNodeHttp.ts (1)
packages/node-fetch/src/utils.ts (1) (1)
  • isNodeReadable (34-36)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: e2e / azure-function
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: node-fetch (noConsumeBody)
🔇 Additional comments (1)
packages/node-fetch/src/Body.ts (1)

171-172:

Details

✅ Verification successful

Good fix for the hanging issue.

This change properly handles the case when the readable stream is destroyed before method calls, which resolves the hanging issue described in the PR. By initializing this._chunks at the beginning and checking if the readable stream is destroyed, you ensure that an empty array is returned rather than hanging indefinitely.

Let's verify that this change properly handles destroyed readable streams:


🏁 Script executed:

#!/bin/bash
# Check if there are other places in the codebase that handle destroyed readable streams
rg "readable\?\.\s*destroyed" --type ts

Length of output: 220


Fix Verified: Improved Handling for Destroyed Streams

The updated code now initializes this._chunks and checks for a destroyed readable stream using if (!_body || _body.readable?.destroyed), which prevents the hanging issue as intended. This approach is consistent with similar checks elsewhere in the codebase (e.g., the check if (this._generatedBody?.readable?.destroyed && this._buffer) in the same file).

Copy link
Copy Markdown
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 (2)
packages/node-fetch/src/Body.ts (1)

474-488: Graceful handling of IncomingMessage with PassThrough and abort signals.

The new code properly pipes the IncomingMessage through a PassThrough with signal. Be aware that @ts-expect-error may conceal type mismatches if Node typings evolve. Regularly confirm that signal usage is properly recognized by the environment.

One minor suggestion: consider adding explicit error handling or logging if the IncomingMessage is destroyed prematurely, for easier debugging.

packages/server/src/utils.ts (1)

226-229: Aborting piped PassThrough streams.

Piping rawRequest to a PassThrough with { signal: controller.signal } correctly enables request abortion. However, ensure that any unhandled rejections or errors on rawRequest also close the PassThrough to prevent memory leaks.

Consider adding explicit error event listeners on rawRequest or the PassThrough stream to handle abrupt termination, ensuring resources are released promptly.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0a8732f and 458c0d5.

📒 Files selected for processing (3)
  • packages/node-fetch/src/Body.ts (8 hunks)
  • packages/server/src/utils.ts (2 hunks)
  • packages/server/test/reproductions.spec.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/test/reproductions.spec.ts
🧰 Additional context used
🪛 GitHub Actions: e2e
packages/server/src/utils.ts

[warning] 1-1: The package 'node:stream' wasn't found on the file system but is built into node. Your Worker may throw errors at runtime unless you enable the 'nodejs_compat' compatibility flag.

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
🔇 Additional comments (9)
packages/node-fetch/src/Body.ts (8)

3-4: Note the warning about node:stream usage in certain environments.

Since node:stream is built into Node.js and might require a specific compatibility flag in certain runtimes (for instance, "nodejs_compat"), ensure that the execution environment permits usage of built-in modules without errors.

Please confirm if the runtime environment (like a worker or edge environment) enables Node.js compatibility for node:stream.


48-48: Optional abort signal is a good improvement for concurrency.

Introducing the optional signal?: AbortSignal; helps handle request abortion gracefully, preventing potential memory leaks or hanging streams when operations are canceled.


60-63: Validate signal propagation.

The call to processBodyInit with options?.signal is appropriate. Just ensure that the rest of the code path checks the signal?.aborted state and handles it cleanly.

Please confirm any upstream usage verifying that any abort event on signal properly terminates the request streams.


176-176: Ensure chunk re-initialization logic is correct.

Re-initializing _chunks to an empty array before collecting avoids data carryover from previous reads. This looks correct, but be mindful if _collectChunksFromReadable can be called multiple times concurrently.


380-383: Updated function signature for processBodyInit improves clarity.

Adding signal?: AbortSignal clarifies intent and allows for more robust abort handling.


411-423: Buffer handling and readable creation.

The logic to convert Buffer to a readable stream using Readable.from(buffer) is straightforward. This helps unify the code path for subsequent operations. Ensure large buffers don’t cause performance or memory issues when read repeatedly.


440-447: Reusing an existing PonyfillReadableStream is efficient.

Returning the same PonyfillReadableStream if bodyInit is already a PonyfillReadableStream avoids unnecessary overhead. This is a clean approach.


449-458: Blob handling looks consistent.

Calling blob.stream() to generate the readable stream seamlessly unifies the flow with other body types. This is an idiomatic approach for Blob data.

packages/server/src/utils.ts (1)

4-4: Same note about node:stream compatibility.

As mentioned in the previous file, ensure the target environment supports built-in modules like node:stream. The pipeline warnings suggest enabling compatibility if needed.

Copy link
Copy Markdown
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

🔭 Outside diff range comments (1)
packages/node-fetch/src/Body.ts (1)

1-586: 💡 Verification agent

🧩 Analysis chain

Consider adding tests for AbortSignal integration.

The implementation now supports AbortSignal throughout the body processing logic, but it would be valuable to ensure there are corresponding tests that verify the behavior when a signal is aborted during body processing.


🏁 Script executed:

#!/bin/bash
# Check if there are any tests for the AbortSignal functionality

# Search for test files
echo "Searching for tests related to AbortSignal..."
rg -l "AbortSignal|signal.*abort|aborted.*signal" --type ts --type js --glob "*/tests/*"

# Look for test cases that specifically test the abort functionality with body processing
echo "Searching for test cases for abort functionality in body processing..."
rg -A 10 -B 2 "body.*signal.*abort|abort.*body.*signal" --type ts --type js --glob "*/tests/*"

Length of output: 737


Action Required: Add Tests for AbortSignal Integration

The implementation correctly propagates the AbortSignal through body processing; however, our investigation did not reveal any tests validating abort behavior. Please add tests to verify that aborting the signal during various stages (e.g., during stream generation and chunk collection) results in the expected cancellation or error handling.

  • Location: packages/node-fetch/src/Body.ts
  • Recommendation: Create unit/integration tests to simulate abort scenarios (for example, with string, buffer, and stream bodies) to ensure AbortSignal triggers abort behavior as intended.
🧹 Nitpick comments (2)
.changeset/heavy-worms-battle.md (1)

6-6: Grammar correction needed.

The sentence structure can be improved for better clarity.

-This PR prevents it to hang and throw an error if the readable stream is destroyed earlier.
+This PR prevents it from hanging and throws an error if the readable stream is destroyed earlier.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~6-~6: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

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

485-509: Consider adding error message for destroyed streams.

When a stream is destroyed prematurely, a more descriptive error message could be useful for debugging.

  pipeline(bodyInit, passthrough, {
    end: true,
    signal,
  })
    .then(() => {
      if (!bodyInit.destroyed) {
        bodyInit.resume();
      }
    })
    .catch(e => {
+     // If no specific error and the stream is destroyed, provide a clearer message
+     if (!e && bodyInit.destroyed) {
+       e = new Error('The readable stream was destroyed before it could be processed');
+     }
      passthrough.destroy(e);
    });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a4f8cfe and c3e1281.

📒 Files selected for processing (7)
  • .changeset/heavy-worms-battle.md (1 hunks)
  • packages/node-fetch/src/Body.ts (10 hunks)
  • packages/node-fetch/src/fetchNodeHttp.ts (3 hunks)
  • packages/node-fetch/tests/fetch.spec.ts (2 hunks)
  • packages/server/src/utils.ts (0 hunks)
  • packages/server/test/reproductions.spec.ts (2 hunks)
  • packages/server/test/test-fetch.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/server/src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/server/test/test-fetch.ts
  • packages/node-fetch/tests/fetch.spec.ts
  • packages/server/test/reproductions.spec.ts
  • packages/node-fetch/src/fetchNodeHttp.ts
🧰 Additional context used
🧬 Code Definitions (1)
packages/node-fetch/src/Body.ts (3)
packages/promise-helpers/src/index.ts (2)
  • MaybePromise (1-1)
  • handleMaybePromise (29-42)
packages/node-fetch/src/Blob.ts (2)
  • PonyfillBlob (72-321)
  • PonyfillBlob (323-326)
packages/node-fetch/src/ReadableStream.ts (1)
  • PonyfillReadableStream (58-246)
🪛 LanguageTool
.changeset/heavy-worms-battle.md

[uncategorized] ~6-~6: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
🔇 Additional comments (10)
.changeset/heavy-worms-battle.md (1)

5-16: Good fix and clear explanation of the issue.

The changeset provides a clear explanation of the issue and the solution. The example is helpful in demonstrating the behavior change from hanging to throwing an error when a Request method is called outside the server adapter scope.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~6-~6: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

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

3-5: LGTM - Required imports for stream handling.

The added imports are necessary for handling IncomingMessage objects and using the stream pipeline for proper error propagation.


49-49: LGTM - Good addition of AbortSignal support.

Adding the optional signal property to PonyfillBodyOptions allows for cancellation of long-running operations, which is crucial for preventing hanging when streams are destroyed.


61-64: LGTM - Properly propagating the signal to processBodyInit.

The constructor now correctly passes the signal from options to the processBodyInit function, ensuring abort signals are properly used throughout the body processing logic.


143-143: LGTM - Improved type definition for chunks.

Using MaybePromise<Uint8Array[]> type allows for better handling of both synchronous and asynchronous chunk collections.


145-190: LGTM - Well-structured chunk collection implementation.

The implementation of _doCollectChunksFromReadableJob properly handles different stream types and ensures chunks are correctly accumulated. The error handling has been improved to properly propagate errors when streams are destroyed.


192-198: LGTM - Effective memoization pattern.

The refactored _collectChunksFromReadable method implements a solid memoization pattern that prevents redundant chunk collection operations. The check for existing chunks before initiating a new collection job is an important optimization.


391-394: LGTM - Updated function signature with AbortSignal support.

The updated signature of processBodyInit now includes the optional AbortSignal parameter, allowing for proper cancellation of stream processing.


423-423: LGTM - Improved type annotations for better code clarity.

The added type annotations across various sections enhance code readability and type safety, making the code easier to understand and maintain.

Also applies to: 430-430, 451-452, 454-454, 460-461, 466-466


485-509: Well-implemented handling of IncomingMessage.

This is the core fix for the hanging issue. The implementation properly handles IncomingMessage instances by:

  1. Creating a PassThrough stream that respects the abort signal
  2. Using pipeline to safely pipe data with proper error propagation
  3. Cleaning up resources by resuming the original stream when needed
  4. Propagating errors to the PassThrough stream

The implementation ensures that if the readable stream is destroyed prematurely, an error will be thrown instead of hanging indefinitely.

Copy link
Copy Markdown
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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7d13d03 and a3fb5bb.

📒 Files selected for processing (1)
  • packages/server/test/test-server.ts (3 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
packages/server/test/test-server.ts (2)
packages/server/src/createServerAdapter.ts (1)
  • DisposableSymbols (473-478)
packages/server/src/types.ts (2)
  • ServerAdapter (127-132)
  • ServerAdapterBaseObject (12-22)
🪛 Biome (1.9.4)
packages/server/test/test-server.ts

[error] 188-188: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / deno
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: type check
  • GitHub Check: server (ponyfill)
  • GitHub Check: e2e / azure-function
🔇 Additional comments (6)
packages/server/test/test-server.ts (6)

127-127: Looks good: Unconditional Node.js HTTP server registration.

Moving the Node.js HTTP server implementation assignment outside of conditional blocks ensures this server type is always available for testing, which aligns with the PR objective to address request handling issues.


129-180: Good implementation of Express test server.

The Express test server implementation follows the established pattern for test servers and properly handles resource cleanup with socket management and server shutdown.


190-227: Good implementation of Fastify route handler and server setup.

The implementation correctly handles all HTTP methods and properly manages the adapter pattern for request processing. The URL host replacement from '127.0.0.1' to 'localhost' ensures consistent behavior across test environments.


228-263: Proper resource cleanup in Fastify server implementation.

The implementation correctly handles async disposal, socket cleanup, and server shutdown. The adapter pattern is properly implemented for context handling.


265-295: Good runtime-specific server implementation organization.

The conditional logic for including Bun and Deno server implementations based on the runtime environment is well structured and maintainable.


516-516: Simplified server name handling.

Directly returning the server implementation name instead of accessing it through the global server map is a good simplification that eliminates potential issues with property access.

Comment thread packages/server/test/test-server.ts Outdated
Copy link
Copy Markdown
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/server/test/test-server.ts (1)

150-150: Consider more robust server port access pattern

The current pattern using optional chaining followed by type assertion could cause runtime errors if server is unexpectedly undefined. Since this is used in a property that might be accessed multiple times, consider using a more defensive approach.

-    url: `http://localhost:${(server?.address() as AddressInfo).port}`,
+    url: `http://localhost:${server && (server.address() as AddressInfo)?.port || 0}`,
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8ff5817 and 1b681df.

📒 Files selected for processing (2)
  • packages/server/test/node.spec.ts (4 hunks)
  • packages/server/test/test-server.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/test/node.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: e2e / azure-function
  • GitHub Check: server (native)
🔇 Additional comments (5)
packages/server/test/test-server.ts (5)

129-180: Good addition of Express server implementation

The Express server adapter implementation is well-structured and follows the same pattern as other server adapters in the file. It correctly handles resource cleanup, socket tracking, and implements the required TestServer interface methods.


189-189: Replace {} with a more precise type for stricter object typing

It's recommended not to use {} since it matches any non-nullish value. Based on TypeScript best practices, please update the adapter declaration by replacing {} with Record<string, unknown> to enforce that only objects are passed.

-      ServerAdapterBaseObject<{}>
+      ServerAdapterBaseObject<Record<string, unknown>>
🧰 Tools
🪛 Biome (1.9.4)

[error] 189-189: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


299-299: Replace {} with a more precise type for stricter object typing

It's recommended not to use {} since it matches any non-nullish value. Based on TypeScript best practices, please update the adapter declaration by replacing {} with Record<string, unknown> to enforce that only objects are passed.

-    let adapter: ServerAdapter<Context, ServerAdapterBaseObject<{}>>;
+    let adapter: ServerAdapter<Context, ServerAdapterBaseObject<Record<string, unknown>>>;
🧰 Tools
🪛 Biome (1.9.4)

[error] 299-299: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


392-392: Replace {} with a more precise type for stricter object typing

It's recommended not to use {} since it matches any non-nullish value. Based on TypeScript best practices, please update the adapter declaration by replacing {} with Record<string, unknown> to enforce that only objects are passed.

-    let adapter: ServerAdapter<HapiContext, ServerAdapterBaseObject<{}>>;
+    let adapter: ServerAdapter<HapiContext, ServerAdapterBaseObject<Record<string, unknown>>>;
🧰 Tools
🪛 Biome (1.9.4)

[error] 392-392: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


127-127: Server implementation conditionals are now more organized

The reorganization of server implementation assignments with clear conditionals (Bun/Deno/others) improves maintainability and readability. Moving the node:http assignment outside the conditionals ensures it's always available for testing.

Also applies to: 267-297

Comment thread packages/server/test/test-server.ts
Copy link
Copy Markdown
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 (3)
packages/node-fetch/src/Body.ts (2)

142-142: Initialization of _chunks with MaybePromise<Uint8Array[]> | null.
Deferring chunk collection is helpful for stream-based bodies. Just be cautious about concurrent calls to _collectChunksFromReadable() which might trigger multiple reads.

Consider adding a short comment clarifying concurrency assumptions if multiple consumers attempt to collect chunks simultaneously.


178-183: Chunk collection from classic readable stream.
The approach of storing all chunks in an array is straightforward. If you expect very large streams, consider a streamed approach or partial read to avoid high memory usage.

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

95-110: Use of handleMaybePromise in readImpl.
This ensures consistent error handling and asynchronous operation chaining. Looks good, but confirm that partial reads won’t be lost if controller._flush() is called during pull.

Include a comment or test block verifying correct partial read behavior.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1b681df and e214682.

📒 Files selected for processing (2)
  • packages/node-fetch/src/Body.ts (10 hunks)
  • packages/node-fetch/src/ReadableStream.ts (3 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
packages/node-fetch/src/Body.ts (4)
packages/promise-helpers/src/index.ts (3)
  • MaybePromise (1-1)
  • handleMaybePromise (29-42)
  • fakePromise (47-94)
packages/node-fetch/src/utils.ts (1)
  • fakePromise (28-28)
packages/node-fetch/src/Blob.ts (2)
  • PonyfillBlob (72-321)
  • PonyfillBlob (323-326)
packages/node-fetch/src/ReadableStream.ts (1)
  • PonyfillReadableStream (59-269)
packages/node-fetch/src/ReadableStream.ts (1)
packages/promise-helpers/src/index.ts (1)
  • handleMaybePromise (29-42)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: unit / deno
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 18
🔇 Additional comments (12)
packages/node-fetch/src/Body.ts (9)

3-4: Imports look fine.
The addition of IncomingMessage, PassThrough, and Readable supports the new logic for piping incoming message streams.


48-48: Ensure the abort signal is fully honored.
With signal?: AbortSignal now available in PonyfillBodyOptions, confirm that downstream code properly checks for signal.aborted and aborts reading if triggered.

Do you have tests confirming an abort event is correctly handled?


60-63: Signal forwarding to processBodyInit.
Passing the abort signal into processBodyInit is good for consistent cancellation support. Ensure external user code also has the same signature update to avoid breakage.


144-172: Async iterable chunk collection logic.
This new _doCollectChunksFromReadableJob() method correctly collects data from AsyncIterable<Uint8Array>. The usage of handleMaybePromise ensures consistent handling of both synchronous and asynchronous contexts.


191-197: Refined _collectChunksFromReadable logic.
This method properly avoids re-collecting if _chunks already exists, reducing duplication. It’s a welcome improvement in readability.


390-393: Extended processBodyInit signature.
Including the signal parameter here is aligned with the rest of the code. Keep an eye on any external calls that might miss the updated signature.


422-422: Minor typed buffer assignment.
This typed declaration is harmless and clarifies that bodyInit is indeed a Buffer.


485-492: IncomingMessage piping with support for AbortSignal.
The use of PassThrough with signal is appropriate. Ensure that events like 'error' and 'close' on bodyInit are also handled or bubbled up if needed.


494-502: Returning a PonyfillReadableStream from the piped PassThrough.
This correctly integrates node’s IncomingMessage with the ponyfill’s stream model.

packages/node-fetch/src/ReadableStream.ts (3)

3-3: New import for handleMaybePromise.
Great addition to unify promise-like and synchronous flows. This improves the code’s consistency.


75-94: Refactor to introduce handleStart with handleMaybePromise.
Splitting out initialization logic into handleStart clarifies the startup sequence. Consider verifying that exceptions thrown inside underlyingSource.start get properly handled.


268-269: Added [Symbol.toStringTag] property.
This property improves debugging by showing 'ReadableStream' in logs and inspections. Nicely done.

Copy link
Copy Markdown
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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e214682 and 1c494d8.

📒 Files selected for processing (2)
  • packages/node-fetch/src/Body.ts (10 hunks)
  • packages/server/src/utils.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/src/utils.ts
🧰 Additional context used
🧬 Code Definitions (1)
packages/node-fetch/src/Body.ts (3)
packages/promise-helpers/src/index.ts (3)
  • MaybePromise (1-1)
  • handleMaybePromise (29-42)
  • fakePromise (47-94)
packages/node-fetch/src/Blob.ts (2)
  • PonyfillBlob (72-321)
  • PonyfillBlob (323-326)
packages/node-fetch/src/ReadableStream.ts (1)
  • PonyfillReadableStream (59-269)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 22
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
🔇 Additional comments (9)
packages/node-fetch/src/Body.ts (9)

3-4: New imports added for handling HTTP messages and stream transformation.

The addition of IncomingMessage and PassThrough imports supports the new functionality for handling HTTP request bodies and enabling proper stream cancellation.


48-48: Good addition of AbortSignal support.

Adding the optional signal property to PonyfillBodyOptions is an excellent enhancement that will help prevent hanging when streams are destroyed prematurely, which directly addresses the PR objective.


60-63: Successfully passing AbortSignal to the body processor.

The changes correctly pass the optional AbortSignal to the processBodyInit function, enabling stream abortion capabilities throughout the body processing logic.


142-142: Type improvement for chunks collection.

Changing _chunks to use MaybePromise<Uint8Array[]> allows for more flexible handling of both synchronous and asynchronous chunk collection scenarios.


144-182: Refactored chunk collection with better error handling.

This refactoring improves how chunks are collected from readable streams and now properly handles stream abortions via the signal parameter on line 178.


184-190: Improved chunk collection with early return.

The new implementation of _collectChunksFromReadable now checks if chunks already exist before attempting to collect them, which prevents redundant operations on potentially destroyed streams and addresses the hanging issue mentioned in the PR.


383-386: Function signature updated to support AbortSignal.

The update to processBodyInit function signature to include the optional signal parameter is crucial for enabling abort capabilities during body processing.


477-495: Excellent addition for handling IncomingMessage with abort capabilities.

The new case for handling IncomingMessage instances is well-implemented. It correctly pipes the incoming message through a PassThrough stream configured with the abort signal, enabling proper termination of stream operations when requested.

This is the key implementation that addresses the PR objective of not hanging when a request is leaked and attempted to be parsed outside the server adapter.


415-416: Type annotations and simplified implementations.

The addition of explicit type annotations for variables (buffer, readableStream, blob) and simplified implementation of factory functions improve code readability and maintainability.

Also applies to: 443-447, 452-459

Comment thread packages/node-fetch/src/Body.ts Outdated
Copy link
Copy Markdown
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 (3)
.changeset/heavy-worms-battle.md (1)

6-8: Refine Language for Clarity
The phrasing "prevents it to hang" is grammatically off. Consider rephrasing to "prevents it from hanging" and change "destroyed earlier" to "destroyed early" for improved clarity and readability.

Apply the following diff improvement:

-When any `Request` method is called outside server adapter scope, it used to hang.
-This PR prevents it to hang and throw an error if the readable stream is destroyed earlier.
+When any `Request` method is called outside the server adapter scope, it used to hang.
+This PR prevents it from hanging and throws an error if the readable stream is destroyed early.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~7-~7: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

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

184-186: Remove unnecessary type casting

Now that the signal property is explicitly declared, the cast to (this as any).signal is unnecessary.

-    return _body.readable.toArray({ signal: (this as any).signal }).then(chunks => {
+    return _body.readable.toArray({ signal: this.signal }).then(chunks => {

150-188: Consider adding error handling for stream operations

The chunk collection operation doesn't explicitly handle errors that might occur when the stream is prematurely destroyed. Adding error handling would make the error messages more clear and help with debugging.

  _doCollectChunksFromReadableJob() {
    if (this.bodyType === BodyInitType.AsyncIterable) {
      if (Array.fromAsync) {
        return handleMaybePromise(
          () => Array.fromAsync(this.bodyInit as AsyncIterable<Uint8Array>),
          chunks => {
            this._chunks = chunks;
            return this._chunks;
          },
+         error => {
+           if (error.name === 'AbortError') {
+             throw new TypeError('The operation was aborted', { cause: error });
+           }
+           throw error;
+         }
        );
      }
      // ...rest of the code
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 33d302e and a94234b.

📒 Files selected for processing (15)
  • .changeset/heavy-worms-battle.md (1 hunks)
  • packages/node-fetch/src/Body.ts (10 hunks)
  • packages/node-fetch/src/ReadableStream.ts (3 hunks)
  • packages/node-fetch/src/Request.ts (1 hunks)
  • packages/node-fetch/src/Response.ts (2 hunks)
  • packages/node-fetch/src/fetchNodeHttp.ts (2 hunks)
  • packages/node-fetch/tests/fetch.spec.ts (2 hunks)
  • packages/server/README.md (1 hunks)
  • packages/server/src/utils.ts (5 hunks)
  • packages/server/test/abort.spec.ts (1 hunks)
  • packages/server/test/compression.spec.ts (3 hunks)
  • packages/server/test/node.spec.ts (5 hunks)
  • packages/server/test/reproductions.spec.ts (2 hunks)
  • packages/server/test/test-fetch.ts (2 hunks)
  • packages/server/test/test-server.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/node-fetch/tests/fetch.spec.ts
  • packages/server/test/reproductions.spec.ts
  • packages/server/src/utils.ts
  • packages/node-fetch/src/Response.ts
  • packages/server/README.md
  • packages/server/test/test-fetch.ts
  • packages/node-fetch/src/fetchNodeHttp.ts
  • packages/node-fetch/src/Request.ts
  • packages/node-fetch/src/ReadableStream.ts
  • packages/server/test/test-server.ts
🧰 Additional context used
🧬 Code Definitions (1)
packages/server/test/node.spec.ts (4)
packages/server/src/utils.ts (1)
  • createDeferredPromise (13-13)
packages/server/src/createServerAdapter.ts (1)
  • createServerAdapter (535-535)
packages/fetch/dist/esm-ponyfill.js (4)
  • Response (4-4)
  • ReadableStream (6-6)
  • fetch (1-1)
  • URL (20-20)
packages/server/test/test-server.ts (1)
  • fetch (48-50)
🪛 LanguageTool
.changeset/heavy-worms-battle.md

[uncategorized] ~7-~7: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...it used to hang. This PR prevents it to hang and throw an error if the readable stre...

(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)

🪛 Biome (1.9.4)
packages/server/test/node.spec.ts

[error] 24-24: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 20
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 18
  • GitHub Check: type check
  • GitHub Check: prettier
  • GitHub Check: server (undici)
  • GitHub Check: e2e / azure-function
🔇 Additional comments (19)
.changeset/heavy-worms-battle.md (2)

1-4: Patch Metadata is Correctly Specified
The changeset correctly indicates that both @whatwg-node/node-fetch and @whatwg-node/server are patched. No action needed here.


9-17: Effective Demonstration in Code Snippet
The provided TypeScript code snippet successfully illustrates the issue and its resolution. The snippet shows that calling request.text() now throws an error as expected.

packages/server/test/abort.spec.ts (3)

5-5: Good utility function addition for conditional test skipping.

Adding a skipIf utility is a clean way to handle conditional test execution based on runtime environments.


8-8: Improved parameter signature enhances test flexibility.

Updating the runTestsForEachServerImpl function to receive serverImplName allows for more precise control over which tests run in specific server environments.


10-13: Well-implemented conditional test skipping logic.

This condition correctly prevents test execution in mismatched environments (Bun tests on non-Bun environments and Deno tests on non-Deno environments), which helps avoid the hanging issue when request methods are used outside their proper adapter scope.

packages/server/test/node.spec.ts (4)

97-97: Good utility function addition for conditional test skipping.

The skipIf function provides a clean way to handle conditional test execution based on runtime environments.


98-150: Well-implemented test for handling cancellation of streaming responses.

This test properly verifies that the application correctly handles cancellation of incremental responses without hanging, which directly addresses the PR objective. The test creates a readable stream, reads a few chunks, and then aborts the request to simulate the scenario described in the PR.


236-273: Good test enhancement for verifying Request.signal behavior inside adapters.

This test ensures that Request.signal is properly handled within the adapter, which is important for ensuring proper error propagation instead of hanging when a request is aborted.


405-418: Well-implemented conditional skipping for IPv6 test.

This change ensures that IPv6 tests only run in environments where they are supported, preventing potential hanging issues in incompatible environments.

packages/server/test/compression.spec.ts (2)

150-150: Improved parameter signature enhances test flexibility.

Updating the runTestsForEachServerImpl function to receive serverImplName allows for more precise control over which compression tests run in specific server environments.


227-295: Enhanced error handling and conditional test execution for compression tests.

The changes provide better error handling with explicit JSON parsing error messages and conditionally skip tests on incompatible server implementations. The code also improves the test structure by handling the 'none' encoding case separately and implementing proper stream handling for compression tests.

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

48-48: Good addition of signal property to PonyfillBodyOptions

Adding the optional AbortSignal to the options interface allows for proper cancellation of ongoing operations, which aligns with the PR's goal of preventing hanging when requests are leaked.


55-55: Nice explicit declaration of the signal property

Explicitly declaring and initializing the signal property addresses the previous review comment about clarifying where this property comes from, making the code more maintainable.

Also applies to: 61-61


89-91: Good signal handling for readable streams

This change ensures that the abort signal is properly connected to the readable stream, allowing operations to be cancelled when the signal is triggered.


148-148: Good refactoring for chunk collection

Changing the _chunks type to MaybePromise<Uint8Array[]> | null and restructuring the collection logic improves code clarity and prevents redundant collection operations.

Also applies to: 190-196


483-505: Excellent handling of IncomingMessage with abort signal

This new case properly handles IncomingMessage instances by piping them through a PassThrough stream with signal support. This prevents hanging when the stream is prematurely destroyed and ensures proper error propagation.


389-391: Good function signature update

Updating the processBodyInit function to accept an abort signal parameter allows for proper signal propagation throughout the body processing pipeline.


421-422: Good cleanup of variable assignments

These small variable assignments improve code readability by clearly identifying the types of the values being used, making the code more maintainable.

Also applies to: 428-429, 449-450, 452-453, 458-459, 464-465


3-4: Comprehensive abort signal implementation

The changes to support abort signals throughout the codebase are thorough and well-implemented. From importing the necessary utilities, to initializing the signal in the constructor, to properly connecting it to streams, these changes ensure that operations can be cancelled properly instead of hanging.

Also applies to: 62-65, 493-495

Comment thread packages/server/test/node.spec.ts Outdated
Copy link
Copy Markdown
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)
packages/node-fetch/src/Body.ts (1)

184-186: Directly reference the signal property instead of using type assertion.

The code uses (this as any).signal when we have a properly declared signal property that could be accessed directly.

-    return _body.readable.toArray({ signal: (this as any).signal }).then(chunks => {
+    return _body.readable.toArray({ signal: this.signal }).then(chunks => {
       this._chunks = chunks;
       return this._chunks;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a94234b and 7c1695b.

📒 Files selected for processing (2)
  • packages/node-fetch/src/Body.ts (10 hunks)
  • packages/server/test/node.spec.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/test/node.spec.ts
🧰 Additional context used
🧬 Code Definitions (1)
packages/node-fetch/src/Body.ts (3)
packages/promise-helpers/src/index.ts (2)
  • MaybePromise (1-1)
  • handleMaybePromise (29-42)
packages/node-fetch/src/Blob.ts (2)
  • PonyfillBlob (72-321)
  • PonyfillBlob (323-326)
packages/node-fetch/src/ReadableStream.ts (1)
  • PonyfillReadableStream (59-269)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 18
  • GitHub Check: type check
  • GitHub Check: prettier
🔇 Additional comments (11)
packages/node-fetch/src/Body.ts (11)

3-4: New imports support HTTP message handling and abort signals.

These imports enable the new functionality for handling IncomingMessage instances and supporting abort signals to cancel operations.


48-48: Good addition of AbortSignal to options interface.

Adding the optional signal property to the PonyfillBodyOptions interface allows consumers to control the lifecycle of body processing operations, which is crucial for preventing hanging when streams are destroyed.


55-55: AbortSignal property added to track cancellation.

The addition of this property enables the class to properly handle cancellation of ongoing operations.


61-65: Proper initialization and propagation of AbortSignal.

The constructor now correctly initializes the signal property from options and passes it to the body processing function.


89-91: Stream and signal integration enhances cancellation support.

This change properly connects the AbortSignal to the Readable stream, ensuring that if the signal is aborted, the stream operations will also be canceled, preventing potential hangs.


148-148: Type update enables more flexible chunk handling.

Changing the _chunks type to include MaybePromise allows for both synchronous and asynchronous chunk collection, making the code more flexible.


150-188: Extract chunk collection logic for better organization.

The new method encapsulates the logic for collecting chunks from various body sources, making the code more maintainable.


190-196: Improved check for existing chunks avoids duplicate work.

The refactored method now properly checks if chunks are already collected before initiating a new collection job, which prevents unnecessary work and potential race conditions.


389-392: Function signature update allows for abort signal propagation.

The updated signature enables the propagation of the abort signal to lower-level stream operations.


483-501: Critical fix for IncomingMessage handling prevents hanging.

This is a key addition that directly addresses the PR's main goal. By properly piping the IncomingMessage through a PassThrough stream with an abort signal, the code can now handle cases where the stream is destroyed prematurely without hanging.


421-422: Type refinements improve code clarity and safety.

The explicit type annotations and simplified expressions make the code more readable and type-safe, reducing the risk of runtime errors.

Also applies to: 428-429, 450-450, 453-453, 459-459, 465-465

Copy link
Copy Markdown
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)
packages/node-fetch/src/Body.ts (1)

147-185: Improved chunk collection logic.

The refactored chunk collection logic is more robust, with proper separation of concerns between checking for existing chunks and performing the actual collection job. The code now more efficiently handles AsyncIterable body types and simplifies collection from ReadableStream bodies.

However, there's potential for a minor optimization in error handling.

Consider adding error handling to handle stream errors, particularly for the case where the stream might be aborted:

 return _body.readable.toArray().then(chunks => {
   this._chunks = chunks;
   return this._chunks;
+}).catch(error => {
+  if (error.name === 'AbortError' || (this.signal && this.signal.aborted)) {
+    throw new Error('The operation was aborted', { cause: error });
+  }
+  throw error;
 });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef78ff and fe2e257.

📒 Files selected for processing (1)
  • packages/node-fetch/src/Body.ts (10 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
packages/node-fetch/src/Body.ts (3)
packages/promise-helpers/src/index.ts (3)
  • MaybePromise (1-1)
  • handleMaybePromise (29-42)
  • fakePromise (47-94)
packages/node-fetch/src/Blob.ts (3)
  • buffer (90-149)
  • PonyfillBlob (72-321)
  • PonyfillBlob (323-326)
packages/node-fetch/src/ReadableStream.ts (1)
  • PonyfillReadableStream (59-269)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 20
  • GitHub Check: unit / node 18
  • GitHub Check: e2e / azure-function
🔇 Additional comments (11)
packages/node-fetch/src/Body.ts (11)

3-4: Good addition of required imports for the new functionality.

The new imports for IncomingMessage and PassThrough support the handling of HTTP message bodies and stream processing, which directly addresses the issue described in the PR objectives.


48-48: Great enhancement to support AbortSignal.

Adding the signal option to PonyfillBodyOptions allows proper cancellation of operations, which is essential for preventing the hanging behavior mentioned in the PR objectives.


55-55: Well-typed property for the abort signal.

The signal property is properly typed with appropriate nullable and optional modifiers, maintaining type safety.


61-65: Properly initializing and passing the abort signal.

The signal is correctly initialized from options and passed to the processBodyInit function, ensuring that abort signals are propagated throughout the body processing pipeline.


145-145: Good type enhancement for _chunks property.

Using MaybePromise<Uint8Array[]> instead of just Uint8Array[] makes the property capable of handling both synchronous arrays and promises for arrays, which improves flexibility when dealing with asynchronous operations.


187-193: Effective caching mechanism for collected chunks.

The new implementation intelligently checks if chunks are already collected before initiating a new collection job, which prevents redundant work and potential race conditions.


386-389: Good function signature update to support AbortSignal.

The processBodyInit function now accepts an optional AbortSignal parameter, allowing for proper propagation of cancellation signals throughout the body processing pipeline.


418-428: Improved Buffer handling with clearer type annotations.

The explicit type annotation for the buffer improves code readability and type safety, and the use of Readable.from(buffer) ensures consistent stream creation.


446-450: Better type handling for ReadableStream instances.

The explicit type annotation improves code clarity and the direct return of the readableStream instance in the bodyFactory simplifies the code.


455-462: Improved Blob handling with clearer type annotations.

The explicit type annotation and direct call to blob.stream() simplifies the code and improves readability.


480-498: Critical fix for the hanging issue when request is leaked.

This is the core change that addresses the PR objective. By adding specialized handling for IncomingMessage instances and piping them through a PassThrough stream that respects the abort signal, the code now properly handles the case where a request might be leaked and parsed outside the server adapter scope.

Instead of hanging indefinitely, the operation will now throw an error if the stream is destroyed prematurely, which is a much better user experience and easier to debug.

@ardatan ardatan merged commit ff052a3 into master Mar 25, 2025
25 checks passed
@ardatan ardatan deleted the fix-req-leak branch May 12, 2025 16:19
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