http: attach error handler to socket synchronously in onSocket#61770
Conversation
|
Review requested:
|
|
Can you revert the other change as part of this PR? |
Codecov Report❌ Patch coverage is
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
🚀 New features to boost your workflow:
|
3f0a317 to
cc09fb0
Compare
ab1a46b to
341f0f3
Compare
pimterry
left a comment
There was a problem hiding this comment.
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.
lib/_http_client.js
Outdated
| // 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; |
There was a problem hiding this comment.
I think we could simplify this to just
| // 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)
lib/_http_client.js
Outdated
| // 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); |
There was a problem hiding this comment.
I think we can drop both these lines these entirely:
- This method (
tickOnSocket) is only called fromonSocketNT onSocketNTis only called fromonSocket- 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
This reverts commit d8c00ad.
341f0f3 to
7d78891
Compare
|
Thanks @pimterry for the review! Both suggestions applied:
Updated the commit message as well to reflect the simpler approach. |
|
Landed in 1523d66 |
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: #48771 Refs: #61658 PR-URL: #61770 Refs: #48771 Reviewed-By: Tim Perry <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [node](https://nodejs.org) ([source](https://github.com/nodejs/node)) | minor | `25.6.1` → `25.7.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>nodejs/node (node)</summary> ### [`v25.7.0`](https://github.com/nodejs/node/releases/tag/v25.7.0): 2026-02-24, Version 25.7.0 (Current), @​ruyadorno prepared by @​aduh95 [Compare Source](nodejs/node@v25.6.1...v25.7.0) ##### Notable Changes - \[[`b0a79b10f0`](nodejs/node@b0a79b10f0)] - **(SEMVER-MINOR)** **http2**: add http1Options for HTTP/1 fallback configuration (Amol Yadav) [#​61713](nodejs/node#61713) - \[[`2d874dfb8e`](nodejs/node@2d874dfb8e)] - **(SEMVER-MINOR)** **sea**: support ESM entry point in SEA (Joyee Cheung) [#​61813](nodejs/node#61813) - \[[`ee59127664`](nodejs/node@ee59127664)] - **sqlite**: mark as release candidate (Matteo Collina) [#​61262](nodejs/node#61262) - \[[`608736e19e`](nodejs/node@608736e19e)] - **(SEMVER-MINOR)** **stream**: rename `Duplex.toWeb()` type option to `readableType` (René) [#​61632](nodejs/node#61632) - \[[`a43375999f`](nodejs/node@a43375999f)] - **(SEMVER-MINOR)** **test\_runner**: show interrupted test on SIGINT (Matteo Collina) [#​61676](nodejs/node#61676) ##### Commits - \[[`ab4375e141`](nodejs/node@ab4375e141)] - **benchmark**: add startup benchmark for ESM entrypoint (Joyee Cheung) [#​61769](nodejs/node#61769) - \[[`8d83d8026b`](nodejs/node@8d83d8026b)] - **build**: add temporal test on GHA windows (Chengzhong Wu) [#​61810](nodejs/node#61810) - \[[`aab153eec3`](nodejs/node@aab153eec3)] - **build**: skip sscache action on non-main branches (Joyee Cheung) [#​61790](nodejs/node#61790) - \[[`9e40fb93bc`](nodejs/node@9e40fb93bc)] - **build**: use path-ignore in GHA coverage-windows.yml (Chengzhong Wu) [#​61811](nodejs/node#61811) - \[[`4896653361`](nodejs/node@4896653361)] - **build**: generate\_config\_gypi.py generates valid JSON (Shelley Vohr) [#​61791](nodejs/node#61791) - \[[`bb82b44de0`](nodejs/node@bb82b44de0)] - **build**: build with v8 gdbjit support on supported platform (Joyee Cheung) [#​61010](nodejs/node#61010) - \[[`e7173a093a`](nodejs/node@e7173a093a)] - **build**: show cc outputs when version detection failed (Chengzhong Wu) [#​61700](nodejs/node#61700) - \[[`848050d38f`](nodejs/node@848050d38f)] - **build,win**: add WinGet Visual Studio 2022 Build Tools Edition config (Mike McCready) [#​61652](nodejs/node#61652) - \[[`938841e1cd`](nodejs/node@938841e1cd)] - **crypto**: always return certificate serial numbers as uppercase (Anna Henningsen) [#​61752](nodejs/node#61752) - \[[`dba9001d6f`](nodejs/node@dba9001d6f)] - **deps**: upgrade npm to 11.10.1 (npm team) [#​61892](nodejs/node#61892) - \[[`75c8e18d2f`](nodejs/node@75c8e18d2f)] - **deps**: update nbytes to 0.1.3 (Node.js GitHub Bot) [#​61879](nodejs/node#61879) - \[[`4ca1597f25`](nodejs/node@4ca1597f25)] - **deps**: remove stale OpenSSL arch configs (René) [#​61834](nodejs/node#61834) - \[[`c4f298c729`](nodejs/node@c4f298c729)] - **deps**: update llhttp to 9.3.1 (Node.js GitHub Bot) [#​61827](nodejs/node#61827) - \[[`7d63a2df93`](nodejs/node@7d63a2df93)] - **deps**: V8: cherry-pick [`64b36b4`](nodejs/node@64b36b441179) (Rafael Magrin) [#​61712](nodejs/node#61712) - \[[`241a6b7088`](nodejs/node@241a6b7088)] - **deps**: update googletest to [`5a9c3f9`](nodejs/node@5a9c3f9) (Node.js GitHub Bot) [#​61731](nodejs/node#61731) - \[[`eec896c0e0`](nodejs/node@eec896c0e0)] - **deps**: V8: backport [`6a0a25a`](nodejs/node@6a0a25abaed3) (Vivian Wang) [#​61666](nodejs/node#61666) - \[[`5a9874af09`](nodejs/node@5a9874af09)] - **doc**: clarify status of feature request issues (Antoine du Hamel) [#​61505](nodejs/node#61505) - \[[`0648ac64aa`](nodejs/node@0648ac64aa)] - **doc**: add esm and cjs examples to node:vm (Alfredo González) [#​61498](nodejs/node#61498) - \[[`8b38718294`](nodejs/node@8b38718294)] - **doc**: clarify build environment is trusted in threat model (Matteo Collina) [#​61865](nodejs/node#61865) - \[[`10e86818ee`](nodejs/node@10e86818ee)] - **doc**: remove incorrect mention of `module` in `typescript.md` (Rob Palmer) [#​61839](nodejs/node#61839) - \[[`b50376f527`](nodejs/node@b50376f527)] - **doc**: simplify addAbortListener example (Chemi Atlow) [#​61842](nodejs/node#61842) - \[[`dea0e7a856`](nodejs/node@dea0e7a856)] - **doc**: fix typo in --disable-wasm-trap-handler description (Dmytro Semchuk) [#​61820](nodejs/node#61820) - \[[`57ac1f5aa0`](nodejs/node@57ac1f5aa0)] - **doc**: clean up globals.md (René) [#​61822](nodejs/node#61822) - \[[`4c30d2bb4d`](nodejs/node@4c30d2bb4d)] - **doc**: remove obsolete Boxstarter automated install (Mike McCready) [#​61785](nodejs/node#61785) - \[[`db610b9e32`](nodejs/node@db610b9e32)] - **doc**: clarify async caveats for `events.once()` (René) [#​61572](nodejs/node#61572) - \[[`b4a826b11c`](nodejs/node@b4a826b11c)] - **doc**: update Juan's security steward info (Juan José) [#​61754](nodejs/node#61754) - \[[`7d9cc5dc54`](nodejs/node@7d9cc5dc54)] - **doc**: fix methods being documented as properties in `process.md` (Antoine du Hamel) [#​61765](nodejs/node#61765) - \[[`aa0362c26a`](nodejs/node@aa0362c26a)] - **doc**: add riscv64 info into platform list (Lu Yahan) [#​42251](nodejs/node#42251) - \[[`9b0101b65b`](nodejs/node@9b0101b65b)] - **doc**: fix dropdown menu being obscured at <600px due to stacking context (Jeff) [#​61735](nodejs/node#61735) - \[[`df2c65b3e4`](nodejs/node@df2c65b3e4)] - **doc**: fix spacing in process message event (Aviv Keller) [#​61756](nodejs/node#61756) - \[[`01018559f5`](nodejs/node@01018559f5)] - **doc**: move describe/it aliases section before expectFailure (Luca Raveri) [#​61567](nodejs/node#61567) - \[[`49443583af`](nodejs/node@49443583af)] - **doc**: fix broken links of net.md (YuSheng Chen) [#​61673](nodejs/node#61673) - \[[`af7c927a2a`](nodejs/node@af7c927a2a)] - **doc**: clean up Windows code snippet in `child_process.md` (reillylm) [#​61422](nodejs/node#61422) - \[[`221648a687`](nodejs/node@221648a687)] - **esm**: update outdated FIXME comment in translators.js (Karan Mangtani) [#​61715](nodejs/node#61715) - \[[`4484e14a31`](nodejs/node@4484e14a31)] - **events**: don't call resume after close (Сковорода Никита Андреевич) [#​60548](nodejs/node#60548) - \[[`4cecbe1f53`](nodejs/node@4cecbe1f53)] - **fs**: add `throwIfNoEntry` option for fs.stat and fs.promises.stat (Juan José) [#​61178](nodejs/node#61178) - \[[`2c94967684`](nodejs/node@2c94967684)] - **http**: remove redundant keepAliveTimeoutBuffer assignment (Efe) [#​61743](nodejs/node#61743) - \[[`435f3dd8e4`](nodejs/node@435f3dd8e4)] - **http**: attach error handler to socket synchronously in onSocket (RajeshKumar11) [#​61770](nodejs/node#61770) - \[[`ce0ebd853d`](nodejs/node@ce0ebd853d)] - **http**: fix keep-alive socket reuse race in requestOnFinish (Martin Slota) [#​61710](nodejs/node#61710) - \[[`8103a78b6a`](nodejs/node@8103a78b6a)] - **http2**: add strictSingleValueFields option to relax header validation (Tim Perry) [#​59917](nodejs/node#59917) - \[[`b0a79b10f0`](nodejs/node@b0a79b10f0)] - **(SEMVER-MINOR)** **http2**: add http1Options for HTTP/1 fallback configuration (Amol Yadav) [#​61713](nodejs/node#61713) - \[[`c589b6b23c`](nodejs/node@c589b6b23c)] - **http2**: fix FileHandle leak in respondWithFile (sangwook) [#​61707](nodejs/node#61707) - \[[`df477202ae`](nodejs/node@df477202ae)] - **lib**: reduce cycles in esm loader and load it in snapshot (Joyee Cheung) [#​61769](nodejs/node#61769) - \[[`deda50a819`](nodejs/node@deda50a819)] - **lib**: remove top-level getOptionValue() calls in lib/internal/modules (Joyee Cheung) [#​61769](nodejs/node#61769) - \[[`b1c1ddff79`](nodejs/node@b1c1ddff79)] - **lib**: optimize styleText when validateStream is false (Rafael Gonzaga) [#​61792](nodejs/node#61792) - \[[`df334f7fa0`](nodejs/node@df334f7fa0)] - **meta**: use SCCACHE\_GHA\_ENABLED for shared build workflows (René) [#​61640](nodejs/node#61640) - \[[`e1b2cd605f`](nodejs/node@e1b2cd605f)] - **meta**: bump cachix/install-nix-action from 31.9.0 to 31.9.1 (dependabot\[bot]) [#​61910](nodejs/node#61910) - \[[`24b858547a`](nodejs/node@24b858547a)] - **module**: fix extensionless entry with explicit type=commonjs (Yuya Inoue) [#​61600](nodejs/node#61600) - \[[`4f2f8006bd`](nodejs/node@4f2f8006bd)] - **repl**: fix FileHandle leak in history initialization (sangwook) [#​61706](nodejs/node#61706) - \[[`2d874dfb8e`](nodejs/node@2d874dfb8e)] - **(SEMVER-MINOR)** **sea**: support ESM entry point in SEA (Joyee Cheung) [#​61813](nodejs/node#61813) - \[[`ee59127664`](nodejs/node@ee59127664)] - **sqlite**: mark as release candidate (Matteo Collina) [#​61262](nodejs/node#61262) - \[[`f14ff14473`](nodejs/node@f14ff14473)] - **src**: remove unnecessary `c_str()` conversions in diagnostic messages (Anna Henningsen) [#​61786](nodejs/node#61786) - \[[`26a09e541d`](nodejs/node@26a09e541d)] - **src**: use bool literals in TraceEnvVarOptions (Tobias Nießen) [#​61425](nodejs/node#61425) - \[[`62b0758c47`](nodejs/node@62b0758c47)] - **src**: fix `--build-sea` default executable path (Alex Schwartz) [#​61708](nodejs/node#61708) - \[[`b5724921b1`](nodejs/node@b5724921b1)] - **src**: track allocations made by zstd streams (Anna Henningsen) [#​61717](nodejs/node#61717) - \[[`3d1d1523a5`](nodejs/node@3d1d1523a5)] - **src**: do not store compression methods on Brotli classes (Anna Henningsen) [#​61717](nodejs/node#61717) - \[[`b2915cda77`](nodejs/node@b2915cda77)] - **src**: extract zlib allocation tracking into its own class (Anna Henningsen) [#​61717](nodejs/node#61717) - \[[`3032a7e3c6`](nodejs/node@3032a7e3c6)] - **src**: release memory for zstd contexts in `Close()` (Anna Henningsen) [#​61717](nodejs/node#61717) - \[[`bc2287db74`](nodejs/node@bc2287db74)] - **src**: add more checks and clarify docs for external references (Joyee Cheung) [#​61719](nodejs/node#61719) - \[[`5daf282e33`](nodejs/node@5daf282e33)] - **src**: fix cjs\_lexer external reference registration (Joyee Cheung) [#​61718](nodejs/node#61718) - \[[`fb2db5f947`](nodejs/node@fb2db5f947)] - **src**: support import() and import.meta in embedder-run modules (Joyee Cheung) [#​61654](nodejs/node#61654) - \[[`e146591002`](nodejs/node@e146591002)] - **stream**: fix decoded fromList chunk boundary check (Thomas Watson) [#​61884](nodejs/node#61884) - \[[`065200a5f0`](nodejs/node@065200a5f0)] - **stream**: add fast paths for webstreams read and pipeTo (Matteo Collina) [#​61807](nodejs/node#61807) - \[[`608736e19e`](nodejs/node@608736e19e)] - **(SEMVER-MINOR)** **stream**: rename `Duplex.toWeb()` type option to `readableType` (René) [#​61632](nodejs/node#61632) - \[[`51587d684d`](nodejs/node@51587d684d)] - **test**: fix typos in test files (Daijiro Wachi) [#​61408](nodejs/node#61408) - \[[`17b2361360`](nodejs/node@17b2361360)] - **test**: allow filtering async internal frames in assertSnapshot (Joyee Cheung) [#​61769](nodejs/node#61769) - \[[`3f6a5f5f7f`](nodejs/node@3f6a5f5f7f)] - **test**: unify assertSnapshot stacktrace transform (Chengzhong Wu) [#​61665](nodejs/node#61665) - \[[`c8dac320de`](nodejs/node@c8dac320de)] - **test**: check stability block position in API markdown (René) [#​58590](nodejs/node#58590) - \[[`6809ef8d04`](nodejs/node@6809ef8d04)] - **test**: adapt buffer test for v8 sandbox (Shelley Vohr) [#​61772](nodejs/node#61772) - \[[`60f5771a74`](nodejs/node@60f5771a74)] - **test**: update FileAPI tests from WPT (Ms2ger) [#​61750](nodejs/node#61750) - \[[`d2fef4a31a`](nodejs/node@d2fef4a31a)] - **test**: update WPT for WebCryptoAPI to [`7cbe7e8`](nodejs/node@7cbe7e8ed9) (Node.js GitHub Bot) [#​61729](nodejs/node#61729) - \[[`d7a87f14da`](nodejs/node@d7a87f14da)] - **test**: update WPT for url to [`efb889e`](nodejs/node@efb889eb4c) (Node.js GitHub Bot) [#​61728](nodejs/node#61728) - \[[`b6ae1fc4b8`](nodejs/node@b6ae1fc4b8)] - **test**: split test-embedding.js and run tests in parallel (Joyee Cheung) [#​61571](nodejs/node#61571) - \[[`a43375999f`](nodejs/node@a43375999f)] - **(SEMVER-MINOR)** **test\_runner**: show interrupted test on SIGINT (Matteo Collina) [#​61676](nodejs/node#61676) - \[[`1c02aa09b0`](nodejs/node@1c02aa09b0)] - **test\_runner**: fix suite rerun (Moshe Atlow) [#​61775](nodejs/node#61775) - \[[`47821ec609`](nodejs/node@47821ec609)] - **tools**: switch to ARM runners on GHA jobs (Antoine du Hamel) [#​61903](nodejs/node#61903) - \[[`1630a56370`](nodejs/node@1630a56370)] - **tools**: avoid building twice in coverage jobs (Antoine du Hamel) [#​61899](nodejs/node#61899) - \[[`89318b0a02`](nodejs/node@89318b0a02)] - **tools**: fix auto-start-ci (Antoine du Hamel) [#​61900](nodejs/node#61900) - \[[`ee107f5e84`](nodejs/node@ee107f5e84)] - **tools**: do not checkout repo in `auto-start-ci.yml` (Antoine du Hamel) [#​61874](nodejs/node#61874) - \[[`c2de1fa619`](nodejs/node@c2de1fa619)] - **tools**: cache V8 build on test-shared workflow (Antoine du Hamel) [#​61860](nodejs/node#61860) - \[[`111c77ec94`](nodejs/node@111c77ec94)] - **tools**: automate updates for test/fixtures/test426 (Rich Trott) [#​60978](nodejs/node#60978) - \[[`ea8886f7d5`](nodejs/node@ea8886f7d5)] - **tools**: use ubuntu-slim runner in GHA (Antoine du Hamel) [#​61759](nodejs/node#61759) - \[[`9db82ba786`](nodejs/node@9db82ba786)] - **tools**: bump unist-util-visit in /tools/doc in the doc group (dependabot\[bot]) [#​61646](nodejs/node#61646) - \[[`c8e58c56b9`](nodejs/node@c8e58c56b9)] - **tools**: bump the eslint group in /tools/eslint with 6 updates (dependabot\[bot]) [#​61628](nodejs/node#61628) - \[[`2518ec77e8`](nodejs/node@2518ec77e8)] - **tools**: use ubuntu-slim runner in GHA (Antoine du Hamel) [#​61734](nodejs/node#61734) - \[[`c5ad2beba3`](nodejs/node@c5ad2beba3)] - **tools**: fix small inconsistencies in JSON doc output (Antoine du Hamel) [#​61757](nodejs/node#61757) - \[[`a9f90bee0a`](nodejs/node@a9f90bee0a)] - **tools**: use ubuntu-latest runner in `notify-on-push` workflow (Antoine du Hamel) [#​61742](nodejs/node#61742) - \[[`30e38182d9`](nodejs/node@30e38182d9)] - **watch**: get flags from execArgv (Efe) [#​61779](nodejs/node#61779) - \[[`da1a08a3a5`](nodejs/node@da1a08a3a5)] - **worker**: eliminate race condition in process.cwd() (giulioAZ) [#​61664](nodejs/node#61664) - \[[`dfac82a235`](nodejs/node@dfac82a235)] - **zlib**: add support for brotli compression dictionary (Andy Weiss) [#​61763](nodejs/node#61763) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4zNS4xIiwidXBkYXRlZEluVmVyIjoiNDMuMzUuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6Om1pbm9yIl19-->
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
onSocketandonSocketNT, 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 hadreq.on('error', ...)set up.This happened because:
createConnectionreturns the socket synchronouslyprocess.nextTick(ininternal/streams/destroy)onSocketNTis also deferred viaprocess.nextTick(to set upsocketErrorListener)nextTickfires beforeonSocketNT'snextTick, so no handler is registeredFix
Attach
socketErrorListenersynchronously inonSocket, before theprocess.nextTick(onSocketNT, ...)call. This requires:socket._httpMessage = thisearly (needed bysocketErrorListenerto find the request)tickOnSocketto avoid duplicates_destroyinonSocketNTagainst double-firing ifsocketErrorListeneralready emitted the error(req.socket || socket)._hadErrorsincereq.socketmay not be assigned yet at early error timeTesting
test/parallel/test-http-request-lookup-error-catchable.js(from net: defer synchronous destroy calls in internalConnect #61658)test/parallel/test-http-*.jstests passRefs: #48771
Refs: #61658