Skip to content

Conversation

@mcollina
Copy link
Member

Summary

Fixes #4627

When fetch() is called with redirect: 'error', the requestObject (the Request instance) could be garbage collected while the fetch was still in progress. The requestObject holds an internal AbortController via the kAbortController property. When requestObject was GC'd, the AbortController was also collected, triggering a FinalizationRegistry callback that removed the abort listener from the user's signal. This caused abort signals to have no effect, resulting in connection leaks for SSE streams.

Changes

The fix stores a reference to requestObject in fetchParams, which stays alive for the entire duration of the fetch operation. This prevents premature garbage collection of the requestObject and its internal AbortController.

  • Pass requestObject to the fetching() function
  • Add requestObject parameter to fetching() function signature
  • Include requestObject in fetchParams object

Test Plan

  • Added regression test test/fetch/issue-4627.js that requires --expose-gc flag
  • Test creates an SSE-like streaming server, fetches with redirect: 'error', triggers GC, aborts, and verifies no data is received after abort
  • Both tests pass:
    • abort should work with redirect: error
    • abort should work without redirect: error (control test)

When fetch() is called with redirect: 'error', the requestObject
could be garbage collected while the fetch was still in progress.
This caused the internal AbortController to be collected, which
triggered a FinalizationRegistry callback that removed the abort
listener from the user's signal, making abort signals ineffective.

The fix stores requestObject in fetchParams to keep it alive for
the entire duration of the fetch operation.

Fixes: #4627
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina mcollina marked this pull request as ready for review January 20, 2026 11:19
@mcollina mcollina requested a review from KhafraDev January 20, 2026 11:19
@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.21%. Comparing base (8fd6c43) to head (62a72ad).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4750      +/-   ##
==========================================
- Coverage   93.21%   93.21%   -0.01%     
==========================================
  Files         109      109              
  Lines       33982    33989       +7     
==========================================
+ Hits        31678    31682       +4     
- Misses       2304     2307       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

crossOriginIsolatedCapability
crossOriginIsolatedCapability,
// Keep requestObject alive to prevent its AbortController from being GC'd
// See https://github.com/nodejs/undici/issues/4627
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// See https://github.com/nodejs/undici/issues/4627

I would remove this line as it is just a "diary entry". The corresponding test is having the link and is totally fine.

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.

SSE connection may leak when fetch init.redirect = 'error'

5 participants