Skip to content

net: handle undefined parent in _unrefTimer and _destroy#64644

Open
haggholm wants to merge 1 commit into
nodejs:mainfrom
hightouchio:fix/net-unref-timer-parent-chain
Open

net: handle undefined parent in _unrefTimer and _destroy#64644
haggholm wants to merge 1 commit into
nodejs:mainfrom
hightouchio:fix/net-unref-timer-parent-chain

Conversation

@haggholm

@haggholm haggholm commented Jul 21, 2026

Copy link
Copy Markdown

The fix and approach are from #64491 by @Shivay-98; opening from a fresh branch to land it, since that PR stalled awaiting the requested changes (unrelated commit dropped, failing test addressed, Signed-off-by added).

Socket.prototype._unrefTimer and Socket.prototype._destroy both walk the socket _parent chain with a strict !== null check. When teardown leaves a socket's _parent as undefined (for example a TLS socket layered over another stream), the loop steps onto undefined and throws

TypeError: Cannot read properties of undefined (reading 'Symbol(timeout)')
    at TLSSocket._unrefTimer (node:net)
    at TLSWrap.onStreamRead (node:internal/stream_base_commons)

from an uncaught I/O callback, crashing the process. Switching both loops to a nullish != null check stops the walk on undefined as well.

The original PR fixed only _unrefTimer; its regression test also calls destroy(), which hits the same unpatched loop in _destroy, so that path is fixed and covered here too.

Fixes: #64490
Refs: #64491

Checklist
  • make -j4 test (UNIX) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem. labels Jul 21, 2026
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.14%. Comparing base (ed63b19) to head (6b91d30).
⚠️ Report is 56 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64644      +/-   ##
==========================================
- Coverage   92.01%   90.14%   -1.88%     
==========================================
  Files         381      741     +360     
  Lines      170167   242095   +71928     
  Branches    26075    45552   +19477     
==========================================
+ Hits       156585   218239   +61654     
- Misses      13291    15364    +2073     
- Partials      291     8492    +8201     
Files with missing lines Coverage Δ
lib/net.js 94.37% <100.00%> (+3.37%) ⬆️

... and 514 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.

@haggholm
haggholm force-pushed the fix/net-unref-timer-parent-chain branch from 6b91d30 to ae8a326 Compare July 21, 2026 16:44
@haggholm

Copy link
Copy Markdown
Author

Cc. @aduh95 who was active in the original PR.

Note: I've never contributed to Node and basically do not know what I am doing.

Comment thread lib/net.js Outdated

for (let s = this; s !== null; s = s._parent) {
// Loose equality stops the walk on a nullish `_parent`; teardown may leave
// it `undefined` rather than `null`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actual change and tests look good! 👍

My one concern is the overall description of this, and these comments specifically, because as far as I can tell we never set _parent to undefined. It's exclusively an external thing, where libraries mess with these internals, is that right? It's a bit unclear in the context given, but AFAICT that's the real problem here.

I understand that they do indeed do that (I'd be interested if you have specific examples) and this is unfortunately de-facto public API now so it would be good to defend against this where it's cheap to do it, but I'd rather avoid documenting undefined as expected behaviour, so we stick with just null internally. It's useful if we don't lead future readers on a wild goose chase.

How about changing both comments to something like:

_parent may be null, we use loose != null checks in case external code sets it to undefined

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sounds good. TBH I'm not even sure what the root cause here is—best I’ve been able to figure is that some 3rd party library tries to unset this by assigning undefined, but I do not know what or why. (Our system has hundreds of 3rd party integrations, so the number of connectors and drivers is…unwieldy.) The change from strictly null to nullish seemed safe enough anyway that it was worth it anyway. But yes, agreed on the comments.

@pimterry

Copy link
Copy Markdown
Member

Note: I've never contributed to Node and basically do not know what I am doing.

Thanks for the PR @haggholm, no worries, this looks good to me and all going in the right direction. Very helpful picking up stalled work like this. I have a minor comment here that it'd be nice to clean up (you can just push another commit and they'll be squashed together at merge automatically) but otherwise everything looks great.

Don't worry too much about the test-internet failure, I strongly suspect something flaky is going on there... Could be us, could be github actions, hard to say.

The fix and approach are from nodejs#64491 by Shivay-98; this reopens it to
get it landed, since the original stalled awaiting requested changes.

`Socket.prototype._unrefTimer` and `Socket.prototype._destroy` both walk
the `_parent` chain with a strict `!== null` check. During connection
teardown a socket's `_parent` can be left `undefined` (for example a TLS
socket layered over another stream), so the loop steps onto `undefined`
and reads a property off it, throwing a TypeError:

    Cannot read properties of undefined (reading 'Symbol(timeout)')

from an uncaught I/O callback and crashing the process.

Using a nullish (`!= null`) check terminates the walk on both `null` and
`undefined`.

[petter@hightouch.io: apply the same fix to the identical loop in
`_destroy`, which the original regression test already exercised via
`destroy()`; add direct unit coverage for both paths.]

Fixes: nodejs#64490
Refs: nodejs#64491

Signed-off-by: Petter Häggholm <petter@hightouch.io>
@haggholm
haggholm force-pushed the fix/net-unref-timer-parent-chain branch from ae8a326 to 0fff075 Compare July 21, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error on Socket unref: Cannot read properties of undefined (reading 'Symbol(timeout)')

4 participants