Skip to content
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: Fixes missing 'unpipe' event for pipes made with {end: false} #11876

Closed

Conversation

zaide-chris
Copy link
Contributor

Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: #11837

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

stream

@nodejs-github-bot nodejs-github-bot added the stream Issues and PRs related to the stream subsystem. label Mar 16, 2017
@zaide-chris
Copy link
Contributor Author

I was unsure if I should mark
make -j4 test (UNIX), or vcbuild test (Windows) passes
Completed as 6 tests fail with vcbuild test when I build the master before my changes and then those same 6 also fail after.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

Can you please wrap all the single tests into braces? Something like: https://github.com/nodejs/node/blob/master/test/parallel/test-buffer-badhex.js

@mcollina
Copy link
Member

const dest5 = new NullWriteable();
dest5.on('pipe', common.mustCall(() => {}));
dest5.on('unpipe', () => {
throw new Error('unpipe should not have been emited');
Copy link
Contributor

Choose a reason for hiding this comment

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

s/emited/emitted/

const dest2 = new NullWriteable();
dest2.on('pipe', common.mustCall(() => {}));
dest2.on('unpipe', () => {
throw new Error('unpipe should not have been emited');
Copy link
Contributor

Choose a reason for hiding this comment

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

s/emited/emitted/


const dest2 = new NullWriteable();
dest2.on('pipe', common.mustCall(() => {}));
dest2.on('unpipe', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

You could also use common.mustNotCall('unpipe should not have been emitted') in these cases.

dest.on('unpipe', common.mustCall(() => {}));
src.pipe(dest, {end: false});
setImmediate(() => {
assert.strictEqual(src._readableState.pipesCount, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

this is the only test that fails without this pr

@mcollina
Copy link
Member

This PR needs some more tests related to the actual use case for this to be fixed. What is the actual bug that it is causing to the user?

@mcollina
Copy link
Member

@MylesBorins I would like to run CITGM with the special READABLE_STREAM=disable env variable. How do I do that?

@zaide-chris
Copy link
Contributor Author

zaide-chris commented Mar 19, 2017

This PR needs some more tests related to the actual use case for this to be fixed.

Would you prefer a less abstract test like: https://gist.github.com/zaide-chris/7051b80cc3f34fd0e21be639e9252f85

What is the actual bug that it is causing to the user?

src.pipe(dest) and src.pipe(dest, {end: false}) act differently in more ways then what is listed in the docs so the tests pushed are based on what I wrote to find out exactly how different they are. I could not find any tests that tested for unpipe events in the case of a source stream ending even with a normal src.pipe(dest) that being the 1st test, with the one that currently fails using src.pipe(dest, {end: false}) being the 4th. So I guess the other 4 could be extraneous other then making sure both src.pipe(dest) and src.pipe(dest, {end: false}) act the same when it comes to emitting unpipe events.

@gibfahn
Copy link
Member

gibfahn commented Mar 19, 2017

I would like to run CITGM with the special READABLE_STREAM=disable env variable. How do I do that?

@mcollina you can just type READABLE_STREAM=disable citgm-all in the CITGM_COMMAND field.

@mcollina
Copy link
Member

@gibfahn this is what I'm getting:

+ READABLE_STREAM=disable citgm-all --nodedir=/home/iojs/build/workspace/citgm-smoker/nodes/debian8-64 -v verbose -x /home/iojs/build/workspace/citgm-smoker/nodes/debian8-64/smoker/report.xml -q error
/tmp/hudson5206784139195918878.sh: 10: /tmp/hudson5206784139195918878.sh: READABLE_STREAM=disable: not found
Build step 'Conditional steps (multiple)' marked build as failure
Recording test results
ERROR: Step ‘Publish JUnit test result report’ failed: No test report files were found. Configuration error?
Sending e-mails to: George.Adams@uk.ibm.com gib@uk.ibm.com michael_dawson@ca.ibm.com
Notifying upstream projects of job completion
Finished: FAILURE

@gibfahn
Copy link
Member

gibfahn commented Mar 20, 2017

@mcollina that's odd, doing that works fine locally. I'll have a look.

@gibfahn
Copy link
Member

gibfahn commented Mar 22, 2017

@mcollina for some reason the export command doesn't work through Jenkins, probably because of the way it gets quoted.

I think the only other way to add the variable would be to modify the job before and after running CI. I can do that for you if you'd like.

@mcollina
Copy link
Member

mcollina commented Mar 23, 2017

@gibfahn can we get a separate jobs on Jenkins to run this check?

@gibfahn
Copy link
Member

gibfahn commented Mar 23, 2017

@mcollina adding extra jobs isn't normally a good idea if we can avoid it, otherwise the different jobs tend to get out of sync.

I've added a checkbox to the job that will disable readable stream if checked (defaults to doing nothing). Don't actually have time to check it right now, but try it and let me know if it's broken.

@mcollina
Copy link
Member

mcollina commented Mar 23, 2017

@mcollina
Copy link
Member

mcollina commented Apr 4, 2017

This is LGTM from me, and it seems it might take some more time to get this tested with CITGM properly, so we might get going.

Copy link
Contributor

@cjihrig cjihrig left a comment

Choose a reason for hiding this comment

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

LGTM with a nit.

{
const dest = new NullWriteable();
const src = new NeverEndReadable();
dest.on('pipe', common.mustCall(() => {}));
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this PR was opened, there were some changes to common. We have common.noop now for this type of callback. common.mustCall() can be called with no function, and it will default to common.noop.

TL;DR - common.mustCall(() => {}) throughout this PR can be replaced with common.mustCall().

Copy link
Member

Choose a reason for hiding this comment

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

@zaide-chris would you mind to update this?

@mcollina
Copy link
Member

mcollina commented Apr 5, 2017

Would you prefer a less abstract test like: https://gist.github.com/zaide-chris/7051b80cc3f34fd0e21be639e9252f85

No, maybe something more easy to ready, just showing that 'unpipe' is not emitted.

  Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
  When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: nodejs#11837
@mcollina
Copy link
Member

@mcollina
Copy link
Member

Landed in 9dcf18a

@mcollina mcollina closed this Apr 14, 2017
mcollina pushed a commit to mcollina/node that referenced this pull request Apr 14, 2017
Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: nodejs#11837
PR-URL: nodejs#11876
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
@evanlucas
Copy link
Contributor

This is landing but throws an error in v7.x. It depends on #12027 being backported

@mcollina
Copy link
Member

mcollina commented May 1, 2017

@evanlucas would you prefer me to backport this manually? #12027 seems quite heavy. We should also backport this to v6-lts IMHO.

@evanlucas
Copy link
Contributor

@mcollina that would be great! You can target the v7.x-staging branch. Thanks!

mcollina pushed a commit to mcollina/node that referenced this pull request May 2, 2017
Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: nodejs#11837
PR-URL: nodejs#11876
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
mcollina pushed a commit to mcollina/node that referenced this pull request May 2, 2017
Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: nodejs#11837
PR-URL: nodejs#11876
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
@mcollina
Copy link
Member

mcollina commented May 2, 2017

@evanlucas here it is #12783.

evanlucas pushed a commit that referenced this pull request May 3, 2017
Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: #11837
PR-URL: #11876
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
evanlucas pushed a commit that referenced this pull request May 3, 2017
Currently when the destination emits an 'error', 'finish' or 'close'
event the pipe calls unpipe to emit 'unpipe' and trigger the clean up
of all it's listeners.
When the source emits an 'end' event without {end: false} it calls
end() on the destination leading it to emit a 'close', this will again
lead to the pipe calling unpipe. However the source emitting an 'end'
event along side {end: false} is the only time the cleanup gets ran
directly without unpipe being called. This fixes that so the 'unpipe'
event does get emitted and cleanup in turn gets ran by that event.

Fixes: #11837
PR-URL: #11876
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
@gibfahn
Copy link
Member

gibfahn commented May 15, 2017

Marking as dont-land because we're landing the v7.x backport.

@MylesBorins
Copy link
Contributor

added backported-to-v6.x label. we should only use dont-land when we don't land things :D

@MylesBorins
Copy link
Contributor

MylesBorins commented May 15, 2017

So it appears like the original test is broken and requires common.noop. It also appears that #12783 might be needed as well.

Can someone please manually backport to v6.x

Also appears that #12027 needs to land first as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Readable stream pipe made with {end: false} does not emit an 'unpipe' event on the writable stream.
10 participants