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: only increase awaitDrain once for each pipe destination #7292

Closed
wants to merge 1 commit into from
Closed

stream: only increase awaitDrain once for each pipe destination #7292

wants to merge 1 commit into from

Conversation

davedoesdev
Copy link
Contributor

@davedoesdev davedoesdev commented Jun 13, 2016

Checklist
  • make -j4 test (UNIX) or vcbuild test nosign (Windows) passes
  • a test and/or benchmark is included
  • documentation is changed or added
  • the commit message follows commit guidelines
Affected core subsystem(s)

stream

Description of change

Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single drain
event is emitted, so awaitDrain in this case will never reach zero and we
end up with a permanently paused stream.
#7278

@nodejs-github-bot nodejs-github-bot added the stream Issues and PRs related to the stream subsystem. label Jun 13, 2016
cb();
}, 3);

const pthru = new stream.PassThrough();
Copy link
Member

Choose a reason for hiding this comment

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

Just curious, would a Readable suffice here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I'd have to set its _read to a nullop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess I could try doing the pushs in the _read.

Copy link
Member

Choose a reason for hiding this comment

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

You could use new stream.Readable({read: () => {}}), but you can just leave it as a PassThrough if you prefer, I really was just curious. :)

@addaleax
Copy link
Member

Thanks, generally looking good! If you want, you can add an Fixes: https://github.com/nodejs/node/issues/7278 line to the commit message so the issue will automatically be closed when this is landed.

@davedoesdev
Copy link
Contributor Author

@addaleax thanks for the review. I'll add a commit addressing your comments but it won't be today now.

@davedoesdev
Copy link
Contributor Author

okay I just updated the test

write: common.mustCall((chunk, encoding, cb) => {
if (chunk.length === 32 * 1024) { // first chunk
readable.push(new Buffer(33 * 1024)); // above hwm
return process.nextTick(cb); // let pipe increment awaitDrain
Copy link
Member

Choose a reason for hiding this comment

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

Is this line actually necessary? I think the test still tests what it’s supposed to test if you leave it out, because after the push() call here awaitDrain would already be 2… right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to test the effect (missing third buffer) rather than the cause.
In any case, awaitDrain isn't incremented until _write returns so it won't already be 2.

@addaleax
Copy link
Member

/cc @nodejs/streams

I’d be kind of interested in knowing whether we generally guarantee that .push() and/or .write() are reentrant and can be called recursively or not.

@addaleax
Copy link
Member

Also, I’m not sure why, but this branch seems to be based on a master that’s about a month old – rebasing might be nice for testing this out locally.

@davedoesdev
Copy link
Contributor Author

If push and write aren't reentrant then it'd be great to add it to the API docs.

@davedoesdev
Copy link
Contributor Author

No idea why it's a month old - only forked it yesterday.

@davedoesdev
Copy link
Contributor Author

davedoesdev commented Jun 14, 2016

This branch is now rebased on latest master.

@davedoesdev
Copy link
Contributor Author

@addaleax I removed the process.nextTick since resume/flow takes care of that now in the test. Thanks!

@@ -550,10 +550,12 @@ Readable.prototype.pipe = function(dest, pipeOpts) {
}

src.on('data', ondata);
var increasedAwaitDrain = false;
Copy link
Member

Choose a reason for hiding this comment

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

can you please add a comment on the reason for this variable?

Copy link
Member

Choose a reason for hiding this comment

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

also we probably wanna move up the declaration of increasedAwaitDrain to before the ondata handler is attached. i'm pretty sure that might fire sync in some cases

@mcollina mcollina added the good first issue Issues that are suitable for first-time contributors. label Jun 14, 2016
@mcollina
Copy link
Member

Can you please format the commit message and squash the commits?

@mcollina
Copy link
Member

This bug is "fixed" from readable-stream@2.0.4 to readable.stream@2.1.2, where 2.0.3 and 2.1.3 are broken.

cc @calvinmetcalf

Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single
'drain' event is emitted, so awaitDrain in this case will never reach
zero and we end up with a permanently paused stream.
@addaleax addaleax removed the good first issue Issues that are suitable for first-time contributors. label Jun 14, 2016
@addaleax
Copy link
Member

addaleax commented Jun 14, 2016

@mcollina I assume the “fixed” range corresponds to #2325#6023?

And I’m not sure why you added good first contribution to a PR… I mean, it is a good first contribution, but I think that’s rather used to signal that an issue is up for grabs by newbies? I’m removing it but feel free to re-add.

@mcollina
Copy link
Member

@addaleax exactly. That means if you are depending on readable-stream@~2.0 it's fixed, and broken in readable-stream@~2.1 and readable-stream@^2.1.

(I thought good first contribution was to tag possible future contributors, maybe I got it wrong)

@davedoesdev
Copy link
Contributor Author

Should be squashed and reformatted now.

@mcollina
Copy link
Member

LGTM

@addaleax
Copy link
Member

@addaleax
Copy link
Member

LGTM

@addaleax
Copy link
Member

Landed in b5175e8

Thanks for fixing this bug, @davedoesdev! I believe this is your first commit to core, if so, welcome on board and we hope you find other ways to contribute!

@addaleax addaleax closed this Jun 15, 2016
addaleax pushed a commit that referenced this pull request Jun 15, 2016
Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single
'drain' event is emitted, so awaitDrain in this case will never reach
zero and we end up with a permanently paused stream.

Fixes: #7278
PR-URL: #7292
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
evanlucas pushed a commit that referenced this pull request Jun 16, 2016
Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single
'drain' event is emitted, so awaitDrain in this case will never reach
zero and we end up with a permanently paused stream.

Fixes: #7278
PR-URL: #7292
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
// A readable stream which produces two buffers.
const bufs = [new Buffer(32 * 1024), new Buffer(33 * 1024)]; // above hwm
const readable = new stream.Readable({
read: function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

could have been read() { ^_^

@evanlucas evanlucas mentioned this pull request Jun 16, 2016
MylesBorins pushed a commit that referenced this pull request Jun 23, 2016
Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single
'drain' event is emitted, so awaitDrain in this case will never reach
zero and we end up with a permanently paused stream.

Fixes: #7278
PR-URL: #7292
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
@MylesBorins MylesBorins mentioned this pull request Jun 24, 2016
MylesBorins pushed a commit that referenced this pull request Jun 24, 2016
Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single
'drain' event is emitted, so awaitDrain in this case will never reach
zero and we end up with a permanently paused stream.

Fixes: #7278
PR-URL: #7292
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
MylesBorins pushed a commit that referenced this pull request Jun 24, 2016
Guard against the call to write() inside pipe's ondata pushing more data
back onto the Readable, thus causing ondata to be called again.

This is fine but results in awaitDrain being increased more than once.
The problem with that is when the destination does drain, only a single
'drain' event is emitted, so awaitDrain in this case will never reach
zero and we end up with a permanently paused stream.

Fixes: #7278
PR-URL: #7292
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
@MylesBorins MylesBorins removed their assignment Dec 27, 2016
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.

7 participants