-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stream: save error in state #34103
stream: save error in state #34103
Conversation
This fails with the following: node$ make -j4 && out/Release/node --expose-gc /Users/ronagy/GitHub/nxtedition/node/test/parallel/test-gc-tls-external-memory.js
AssertionError [ERR_ASSERTION]: 0 < 256
at TLSSocket.connect (/Users/ronagy/GitHub/nxtedition/node/test/parallel/test-gc-tls-external-memory.js:36:5)
at TLSSocket.<anonymous> (/Users/ronagy/GitHub/nxtedition/node/test/common/index.js:365:15)
at TLSSocket.emit (events.js:314:20)
at emitErrorNT (internal/streams/destroy.js:127:8)
at emitErrorCloseNT (internal/streams/destroy.js:92:3)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
at runNextTicks (internal/process/task_queues.js:62:3)
at processImmediate (internal/timers.js:435:9) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '=='
} Seems like a GC related bug in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
@nodejs/streams @addaleax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. We should probably run a benchmark just to make sure it's fine.
Just a note, this does not pass CI because of the GC issue. I will need help to sort that out. |
This is blocking #34035 |
Very weird, after looking through heap-dumps of master and this PR it looks like |
@lundibundi Would you mind creating an issue for that? I'll apply your workaround in the meantime. |
That seems to have fixed it. I'd just like a thumbs up from @addaleax before landing this. |
My only concern here is the increased memory usage but I guess it's ok as we are in an error, hopefully uncommon, condition. Is it possible to add a test for #34103 (comment)? |
So … let’s be a bit more clear about the cause of the memory leak here: What’s happening is that the In the test, this turns out to be somewhat catastrophic, because each error’s stack frame contains the previous TLS socket, effectively creating a singly-linked list of all TLS sockets here. So yes, this PR does introduce this problem for a specific reason. I know this is very dependent on the test’s layout, but it’s a problem I could see arising in other situations as well. There’s four possible paths forward that I see here:
I would personally lean towards option 3, but I think any is fine. If we do pick option 2, it probably makes more sense to amend the test code than the tls source code, though. [diff for option 3]diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js
index b63c8f89aec5..223e9af7bc74 100644
--- a/lib/_stream_writable.js
+++ b/lib/_stream_writable.js
@@ -434,12 +434,14 @@ function onwrite(stream, er) {
if (er) {
if (!state.errored) {
state.errored = er;
+ er.stack;
}
// In case of duplex streams we need to notify the readable side of the
// error.
if (stream._readableState && !stream._readableState.errored) {
stream._readableState.errored = er;
+ er.stack;
}
if (sync) {
diff --git a/lib/internal/streams/destroy.js b/lib/internal/streams/destroy.js
index 575b12f9c18b..483b6d11a196 100644
--- a/lib/internal/streams/destroy.js
+++ b/lib/internal/streams/destroy.js
@@ -25,6 +25,7 @@ function destroy(err, cb) {
}
if (err) {
+ err.stack;
if (w && !w.errored) {
w.errored = err;
}
@@ -61,6 +62,7 @@ function _destroy(self, err, cb) {
const w = self._writableState;
if (err) {
+ err.stack;
if (w && !w.errored) {
w.errored = err;
}
@@ -175,6 +177,7 @@ function errorOrDestroy(stream, err, sync) {
if ((r && r.autoDestroy) || (w && w.autoDestroy))
stream.destroy(err);
else if (err) {
+ err.stack;
if (w && !w.errored) {
w.errored = err;
} |
I'm good with this. Would be nice to do 4 regardless. |
@addaleax done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still LGTM
Landed in 6213fce |
Useful for future PR's to resolve situations where e.g. finished() is invoked on an already errored streams. PR-URL: #34103 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Luigi Pinca <[email protected]>
Useful for future PR's to resolve situations where e.g. finished() is invoked on an already errored streams. PR-URL: #34103 Backport-PR-URL: #34887 Refs: #34680 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Luigi Pinca <[email protected]>
Useful for future PR's to resolve situations where e.g. finished() is invoked on an already errored streams. PR-URL: #34103 Backport-PR-URL: #34887 Refs: #34680 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Luigi Pinca <[email protected]>
Notable changes: - buffer: also alias BigUInt methods (Anna Henningsen) #34960 - crypto: add randomInt function (Oli Lalonde) #34600 - perf_hooks: add idleTime and event loop util (Trevor Norris) #34938 - stream: simpler and faster Readable async iterator (Robert Nagy) #34035 - stream: save error in state (Robert Nagy) #34103 PR-URL: #35023
Notable changes: - buffer: also alias BigUInt methods (Anna Henningsen) #34960 - crypto: add randomInt function (Oli Lalonde) #34600 - perf_hooks: add idleTime and event loop util (Trevor Norris) #34938 - stream: simpler and faster Readable async iterator (Robert Nagy) #34035 - stream: save error in state (Robert Nagy) #34103 PR-URL: #35023 Conflicts: src/node_version.h
Notable changes: - buffer: also alias BigUInt methods (Anna Henningsen) nodejs#34960 - crypto: add randomInt function (Oli Lalonde) nodejs#34600 - perf_hooks: add idleTime and event loop util (Trevor Norris) nodejs#34938 - stream: simpler and faster Readable async iterator (Robert Nagy) nodejs#34035 - stream: save error in state (Robert Nagy) nodejs#34103 PR-URL: nodejs#35023 Conflicts: src/node_version.h
There is a downside to the following, beyond the small performance penalty.
As implemented in: node/lib/internal/streams/destroy.js Line 35 in 8a41d9b
If import {Writable} from 'node:stream'
const error = new Error('This failed')
const stream = new Writable()
stream.destroy(error)
stream.on('error', () => {})
error.message = `Additional info: ${error.message}`
console.log(error) // This does not print 'Additional info'
// Error: This failed
// at file:///...
// ... I opened an issue at #51715. |
Useful for future PR's to resolve situations where various API's are invoked on an already errored stream.
Extracted from #34035
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes