Skip to content

Conversation

@RajeshKumar11
Copy link
Contributor

Summary

Fixes the underlying issue identified in #61658 (already merged) where socket errors could be unhandled even when a user had set up a request error handler.

Root Cause

Between onSocket and onSocketNT, the socket had no error handler. If an error was emitted during this window (e.g. from a blocklist check or custom lookup function), it would be unhandled even if the user had req.on('error', ...) set up.

This happened because:

  1. createConnection returns the socket synchronously
  2. Socket errors are deferred via process.nextTick (in internal/streams/destroy)
  3. onSocketNT is also deferred via process.nextTick (to set up socketErrorListener)
  4. The error's nextTick fires before onSocketNT's nextTick, so no handler is registered

Fix

Attach socketErrorListener synchronously in onSocket, before the process.nextTick(onSocketNT, ...) call. This requires:

  • Setting socket._httpMessage = this early (needed by socketErrorListener to find the request)
  • Removing and re-adding the listener in tickOnSocket to avoid duplicates
  • Guarding _destroy in onSocketNT against double-firing if socketErrorListener already emitted the error
  • Using (req.socket || socket)._hadError since req.socket may not be assigned yet at early error time

Testing

Refs: #48771
Refs: #61658

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Feb 11, 2026
@ronag
Copy link
Member

ronag commented Feb 11, 2026

Can you revert the other change as part of this PR?

@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 94.73684% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 89.76%. Comparing base (04946a7) to head (7d78891).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
lib/net.js 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #61770      +/-   ##
==========================================
+ Coverage   89.73%   89.76%   +0.02%     
==========================================
  Files         675      675              
  Lines      204648   204674      +26     
  Branches    39330    39339       +9     
==========================================
+ Hits       183651   183720      +69     
+ Misses      13282    13231      -51     
- Partials     7715     7723       +8     
Files with missing lines Coverage Δ
lib/_http_client.js 97.38% <100.00%> (+0.01%) ⬆️
lib/net.js 94.51% <80.00%> (-0.02%) ⬇️

... and 49 files with indirect coverage changes

🚀 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.

@RajeshKumar11 RajeshKumar11 force-pushed the fix/http-client-socket-error-handler branch from 3f0a317 to cc09fb0 Compare February 11, 2026 08:29
@RajeshKumar11
Copy link
Contributor Author

Done! Rebased onto upstream/main and added a revert of the net.js changes from #61658 (commit d8c00ad). The test from #61658 is retained since the underlying issue is now properly fixed in _http_client.js.

@RajeshKumar11 RajeshKumar11 force-pushed the fix/http-client-socket-error-handler branch 2 times, most recently from ab1a46b to 341f0f3 Compare February 11, 2026 09:25
Copy link
Member

@pimterry pimterry left a comment

Choose a reason for hiding this comment

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

Looks good in general, I think there's just a couple of changes we should make before we merge this to clear things up a bit.

Comment on lines 574 to 576
// Use socket directly as req.socket may not be assigned yet (e.g. when
// the error is emitted before onSocketNT runs).
(req.socket || socket)._hadError = true;
Copy link
Member

Choose a reason for hiding this comment

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

I think we could simplify this to just

Suggested change
// Use socket directly as req.socket may not be assigned yet (e.g. when
// the error is emitted before onSocketNT runs).
(req.socket || socket)._hadError = true;
socket._hadError = true;

I've just had a look, and everywhere that socket._httpMessage is set to a request, req.socket is set at the same time, so if req is set they're always equivalent, and setting this on the socket in the socket error handler makes perfect sense to me conceptually.

(It looks like they could diverge if req were null - that logic is less clear - but that doesn't matter within this if)

// Remove the early error listener attached in onSocket (if any) before
// re-adding it here to avoid duplicate listeners.
socket.removeListener('error', socketErrorListener);
socket.on('error', socketErrorListener);
Copy link
Member

Choose a reason for hiding this comment

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

I think we can drop both these lines these entirely:

  • This method (tickOnSocket) is only called from onSocketNT
  • onSocketNT is only called from onSocket
  • This socket error handler will always have been set exactly once already, so this doesn't really do anything.

Anything I'm missing? This would make sense if the handlers were different, or maybe if there were other code paths through here, but I don't see any.

Between onSocket and onSocketNT, the socket had no error handler,
meaning any errors emitted during that window (e.g. from a blocklist
check or custom lookup) would be unhandled even if the user had set up
a request error handler.

Fix this by attaching socketErrorListener synchronously in onSocket,
setting socket._httpMessage so the listener can forward errors to the
request. The _destroy path in onSocketNT is also guarded to prevent
double-firing if socketErrorListener already emitted the error.

Fixes: nodejs#48771
Refs: nodejs#61658
@RajeshKumar11 RajeshKumar11 force-pushed the fix/http-client-socket-error-handler branch from 341f0f3 to 7d78891 Compare February 12, 2026 14:02
@RajeshKumar11
Copy link
Contributor Author

Thanks @pimterry for the review! Both suggestions applied:

  1. Simplified to socket._hadError = true — you are right that req.socket and socket are always equivalent when req is set via socket._httpMessage.

  2. Removed the removeListener/on pair from tickOnSocket entirely. Since tickOnSocket is only called from onSocketNT (which is only called from onSocket), and onSocket already attaches the listener once, the duplicate setup was indeed redundant.

Updated the commit message as well to reflect the simpler approach.

@pimterry pimterry added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels Feb 12, 2026
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 12, 2026
@nodejs-github-bot
Copy link
Collaborator

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

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants