-
-
Notifications
You must be signed in to change notification settings - Fork 32k
stream: call _final synchronously #30597
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
Conversation
Those failures are very interesting. WIP |
b6777af
to
9cb4bbb
Compare
@mcollina: I think you might be interested in this change. @jasnell @addaleax The http2 failures are very strange to me. The way http2 uses streams a bit of a challenge. My gut feeling is that there is a bug hiding somewhere and this PR activates it. Would either if you be interested in collaborating with me to figure out why this PR would cause the failing tests in http2? |
e.g. in server.on('stream', common.mustCall((stream) => {
// Wait for some data to come through.
setImmediate(() => {
stream.on('close', common.mustCall(() => {
assert.strictEqual(stream.rstCode, 0);
}));
stream.respond({ ':status': 400 });
stream.end();
});
}));
The rest of the failures seem related to this. |
@@ -286,7 +286,8 @@ Object.setPrototypeOf(WriteStream, Writable); | |||
WriteStream.prototype._final = function(callback) { | |||
if (typeof this.fd !== 'number') { | |||
return this.once('open', function() { | |||
this._final(callback); | |||
// Make sure _final is called after every 'open' listener | |||
process.nextTick(() => this._final(callback)); |
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.
Would this maybe be a good reason to keep _final
behind a nextTick, at least in a semver-patch PR? Or maybe, why does this need to be changed here but not in the other places where we use a similar pattern (e.g. net, http2)?
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.
That's one reason to keep _final
behind a nextTick but there are other reasons which make it not a good idea. I don't think this becomes a relevant problem once #29656 is merged.
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.
but not in the other places where we use a similar pattern (e.g. net, http2)?
It should probably be changed in those places as well.
@Trott a WIP label please |
This has some overlap with #30733. This is more about calling |
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.
I’m positive to this change, however I would wait for #29656 to land. I’d just be cautious and flag it semver-major. All things that interact with the timing order are hard to debug.
Using this transform stream could be potentially significantly simplified, see https://github.com/nxtedition/node/blob/stream-transform-simplify/lib/_stream_transform.js (WIP). As well as other "sync" streams. |
1672b25
to
66e2654
Compare
const err = handle.shutdown(req); | ||
if (err === 1) // synchronous finish | ||
return afterShutdown.call(req, 0); | ||
// TODO: Why does this need to be in nextTick? |
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.
@addaleax Maybe you have some insight here?
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.
What happens when it isn’t? 😅
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.
Oh! I forgot about that small detail:
Path: parallel/test-http2-client-socket-destroy
Mismatched noop function calls. Expected exactly 1, actual 0.
at Object.mustCall (/Users/ronagy/GitHub/nxtedition/node/test/common/index.js:324:10)
at Http2Server.<anonymous> (/Users/ronagy/GitHub/nxtedition/node/test/parallel/test-http2-client-socket-destroy.js:19:29)
at Http2Server.<anonymous> (/Users/ronagy/GitHub/nxtedition/node/test/common/index.js:358:15)
at Http2Server.emit (events.js:304:20)
at ServerHttp2Session.sessionOnStream (internal/http2/core.js:2739:19)
at ServerHttp2Session.emit (events.js:304:20)
at emit (internal/http2/core.js:285:8)
at processTicksAndRejections (internal/process/task_queues.js:87:22)
Command: out/Release/node --expose-internals /Users/ronagy/GitHub/nxtedition/node/test/parallel/test-http2-client-socket-destroy.js
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.
Seems like some form of timing issue.
@@ -82,15 +82,15 @@ const t = new stream.Transform({ | |||
process.nextTick(function() { |
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.
This PR makes the order here more sensible
This is semver-major due to the change in timing |
866be29
to
b6f2f1a
Compare
@Trott: node/streams ping? |
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.
I’m not convinced by this change given the impact in our codebase.
My main motivation is that it would be significantly easier to implement streams which need to support both sync and async behavior, e.g. This is currently not possible. It's made worse by the fact that you can't properly emulate this with Though this doesn't actually "fix" anything other than make things easier to maintain. So whether it's worth the risk or not is debatable... |
I think there are more pressing PR's to land so if this is controversial I'm fine with closing this for now and re-open at a more appropriate time. |
The Example: let ticked = false;
const t1 = new Transform({
transform(chunk, encoding, cb) {
cb();
}
});
t1.on('finish', () => {
console.log('t1', ticked);
});
t1.end();
const t2 = new Transform({
transform(chunk, encoding, cb) {
cb();
},
final(callback) {
callback();
}
});
t2.on('finish', () => {
console.log('t2', ticked);
});
t2.end();
ticked = true;
// Prints
// t1 false
// t2 true Though providing a |
b6f2f1a
to
da175f0
Compare
Pushed a |
91618f5
to
5cf54fb
Compare
b43b23e
to
63380c2
Compare
63380c2
to
68c0d3b
Compare
Yea, I still think this is a good idea long term but now that I started digging into it again it's significantly more complicated than I thought. We have higher prio stuff to put energy into. I'll re-open this in the future. |
_final
in a nextTick we just need to emit the event asynchronously. Try to call_final
as soon as possible. With this change it's possible to remove someprefinish
hacks whereprefinish
is used to get a synchronous pre_final
.This is blocking some other pending fixes.
Blocked by #29656
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes