-
Notifications
You must be signed in to change notification settings - Fork 30k
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: fix sync callback leak #31765
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ function setHasTickScheduled(value) { | |
} | ||
|
||
const queue = new FixedQueue(); | ||
let tickId = newAsyncId(); | ||
|
||
// Should be in sync with RunNextTicksNative in node_task_queue.cc | ||
function runNextTicks() { | ||
|
@@ -70,7 +71,7 @@ function processTicksAndRejections() { | |
let tock; | ||
do { | ||
while (tock = queue.shift()) { | ||
const asyncId = tock[async_id_symbol]; | ||
const asyncId = tickId = tock[async_id_symbol]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might not be exactly correct since it will only run if there is a Maybe would require some help from @addaleax if this approach is deemed worth looking further into. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I’m happy to investigate this in more detail… It looks like you’re essentially going for an id for each individual synchronous block of JS execution, which should be doable, but I agree that it makes sense to get a green light for this approach first :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, fwiw I've been mulling over the details of a proposal to TC-39 that does precisely that: assign execution and trigger IDs for every JS execution at the language level. It would make several things significantly easier. I'm not convinced that this PR is the way to go forward tho and definitely need to give it a bit more thought. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm yeah … I’m not sure if this would work fully when using |
||
emitBefore(asyncId, tock[trigger_async_id_symbol], tock); | ||
|
||
try { | ||
|
@@ -179,6 +180,7 @@ module.exports = { | |
// Sets the callback to be run in every tick. | ||
setTickCallback(processTicksAndRejections); | ||
return { | ||
tickId, | ||
nextTick, | ||
runNextTicks | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
|
||
// Tests for the regression in _stream_writable fixed in | ||
// https://github.com/nodejs/node/pull/31756 | ||
|
||
// Specifically, when a write callback is invoked synchronously | ||
// with an error, and autoDestroy is not being used, the error | ||
// should still be emitted on nextTick. | ||
|
||
const { Writable } = require('stream'); | ||
|
||
class MyStream extends Writable { | ||
#cb = undefined; | ||
|
||
constructor() { | ||
super({ autoDestroy: false }); | ||
} | ||
|
||
_write(_, __, cb) { | ||
this.#cb = cb; | ||
} | ||
|
||
close() { | ||
// Synchronously invoke the callback with an error. | ||
this.#cb(new Error('foo')); | ||
} | ||
} | ||
|
||
const stream = new MyStream(); | ||
|
||
const mustError = common.mustCall(2); | ||
|
||
stream.write('test', () => {}); | ||
|
||
// Both error callbacks should be invoked. | ||
|
||
stream.on('error', mustError); | ||
|
||
stream.close(); | ||
|
||
// Without the fix in #31756, the error handler | ||
// added after the call to close will not be invoked. | ||
stream.on('error', mustError); |
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.
@mcollina: one way to get around the
readable-stream
problem could be:The downside would be that it would be slower for sync streams (but probably correct?) on old node versions, i.e . it would always think it is "sync" and use
nextTick
even though it is not strictly necessary.We could wait with backporting this to readable-stream until v14 becomes LTS?
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.
readable-stream has to work on old version of Node.js as well, as long as browsers. I would prefer to avoid this completely.