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: make sure readable is called on empty chunk followed by eof #29762

Closed
wants to merge 4 commits into from

Conversation

ronag
Copy link
Member

@ronag ronag commented Sep 29, 2019

An empty chunk should be treated like any other chunk and trigger the corresponding read logic.

Fixes: #29758

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot nodejs-github-bot added the stream Issues and PRs related to the stream subsystem. label Sep 29, 2019
@ronag ronag force-pushed the readable-empty-chunk branch 2 times, most recently from fdab801 to 2c0ab44 Compare September 29, 2019 15:58
@ronag ronag closed this Sep 29, 2019
@ronag ronag reopened this Sep 29, 2019
@ronag ronag force-pushed the readable-empty-chunk branch 5 times, most recently from a5c58e9 to eebae1f Compare September 29, 2019 20:51
@ronag
Copy link
Member Author

ronag commented Sep 29, 2019

@addaleax @mcollina I think this needs your expertise. I'm not that confident with the Readable stuff but this might be a step in the right direction.

lib/_stream_readable.js Outdated Show resolved Hide resolved
@ronag ronag force-pushed the readable-empty-chunk branch 4 times, most recently from ca14482 to fe92fc7 Compare September 29, 2019 21:23
An empty chunk should be treated like any other chunk and trigger
the read logic.

Fixes: nodejs#29758
@ronag ronag force-pushed the readable-empty-chunk branch 2 times, most recently from 914138e to 26d983f Compare September 29, 2019 21:35
@awwright
Copy link
Contributor

This seems to fix the issue on my end.

Should this also be backported?

@ronag
Copy link
Member Author

ronag commented Sep 29, 2019

@awwright: Thanks for testing! I'm not sure whether this fix will break something else. We will have to wait for someone more familiar with Readable to chime in.

@@ -44,7 +44,7 @@ class TestReader extends R {
}

const reader = new TestReader();
setImmediate(function() {
process.nextTick(function() {
Copy link
Member Author

@ronag ronag Sep 29, 2019

Choose a reason for hiding this comment

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

This test kind of contradicts the added test, i.e. it expects the stream to stop reading after pushing an empty chunk.

Not sure if this is something that indicates whether this fix will cause other problems.

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 articulate this a bit more? Is this change needed to make the test pass?

Copy link
Member Author

@ronag ronag Sep 30, 2019

Choose a reason for hiding this comment

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

Yes, otherwise it live locks. Basically it gets into an infinite loop (with nextTick) between. Because it pushes an empty chunk, which invokes a read call which pushes an empty chunk etc...

By changing it to nextTick instead of setImmediate it's possible to get between that loop and end it. But as I wrote I'm unsure of the implications.

Copy link
Member

Choose a reason for hiding this comment

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

I think we should avoid that live lock in code.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we should avoid that live lock in code.

What does that mean in practice? Should we add a guard? Or is the current state of the PR ok in that regard?

Copy link
Member

Choose a reason for hiding this comment

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

I think @mcollina would like to add a guard in the streams implementation that this can not cause an infinite loop / that this test does not need to change.

@awwright
Copy link
Contributor

Yeah it looks like test/parallel/test-stream2-compatibility.js timed out for me, I suspect if it breaks that it would break someone in the wild

@ronag
Copy link
Member Author

ronag commented Sep 29, 2019

Yeah it looks like test/parallel/test-stream2-compatibility.js timed out for me, I suspect if it breaks that it would break someone in the wild

Yep, the problem is that fixing your issue will per definition break this test, i.e. one of the expects a new read to be scheduled when pushing and empty chunk while the other expects the exact opposite. :/

@awwright
Copy link
Contributor

Hm, I'm not very well versed in the details of streams, but both tests here look reasonable; are they really mutually exclusive? There's got to be something more to this.

@nodejs-github-bot
Copy link
Collaborator

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.

I’m unsure about the semver level of this. This fixes a bug for me, but I’m uncertain if it diverges enough to be considered a major.

@@ -44,7 +44,7 @@ class TestReader extends R {
}

const reader = new TestReader();
setImmediate(function() {
process.nextTick(function() {
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 articulate this a bit more? Is this change needed to make the test pass?

@mcollina
Copy link
Member

Would you mind adding a unit test that only use Readable?

}

// Reset emittedReadable once it is safe to schedule another
// emitReadable.
state.emittedReadable = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

@mcollina: Please also observe this change. I'm pretty sure it's fine but just making sure I have a second opinion on that.

@ronag
Copy link
Member Author

ronag commented Sep 30, 2019

@mcollina: I noticed another possible issue while looking into making that Readable test. I would like to investigate it a bit further to see whether it is a problem. Can we get a WIP label on this so it doesn't land by accident.

@lpinca lpinca added the wip Issues and PRs that are still a work in progress. label Sep 30, 2019
@ronag
Copy link
Member Author

ronag commented Oct 2, 2019

@lpinca: I've been trying to wrap my head around this and I believe something is wrong with DuplexPair.

DuplexPair is as far as I understand under the invariant that every push will result in a _read. However, this breaks down as soon as a push(null) (EOF) is performed (hence the referenced bug).

I think the behaviour of Readable is mostly correct here in that _read is not called since the stream is in ended state. We should NOT call _read in this case since ended means that no further push calls should be performed.

What further confuses me is why this problem is only triggered by empty chunks. As far as I understand empty chunks (for whatever reason) are actually the one behaving correctly.

@lpinca
Copy link
Member

lpinca commented Oct 3, 2019

DuplexPair is as far as I understand under the invariant that every push will result in a _read. However, this breaks down as soon as a push(null) (EOF) is performed (hence the referenced bug).

Yes I agree, that assumption seems wrong.

@ronag
Copy link
Member Author

ronag commented Oct 3, 2019

Ok, I think this PR is not correct then. I will try to dig into this from the other direction.

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. wip Issues and PRs that are still a work in progress.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

stream: certain sequence of Writable#write and Writable#end will not end stream
6 participants