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

Another pipe() deadlock since v14.17 #48666

Closed
kanongil opened this issue Jul 5, 2023 · 4 comments
Closed

Another pipe() deadlock since v14.17 #48666

kanongil opened this issue Jul 5, 2023 · 4 comments
Labels
stream Issues and PRs related to the stream subsystem.

Comments

@kanongil
Copy link
Contributor

kanongil commented Jul 5, 2023

Version

v20.3.1

Platform

Darwin silmaril.home 22.5.0 Darwin Kernel Version 22.5.0: Thu Jun 8 22:22:19 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T8103 arm64

Subsystem

stream

What steps will reproduce the bug?

const { Readable, Writable } = require('stream');

(async () => {

    // Prepare src that is internally ended, with buffered data pending

    const src = new Readable({ _read() {} });
    src.push(Buffer.alloc(100));
    src.push(null);
    src.pause();

    await new Promise((resolve) => setImmediate(resolve));       // Give it time to settle

    const dst = new Writable({
        highWaterMark: 1000,
        write(buf, enc, cb) {

            console.log('write', buf.length);

            // Delay 1 tick to allow writableNeedDrain=true

            process.nextTick(cb);
        }
    });

    dst.write(Buffer.alloc(1000));                               // Fill write buffer

    //src.resume();
    src.pipe(dst);

    await new Promise((resolve) => setImmediate(resolve));       // Give it time to settle

    console.log('src buffer', src.readableLength);
})();

How often does it reproduce? Is there a required condition?

100% for test code since node v14.17.0.

This seems to be a corner case that depends on:

  1. Source stream is already ended.
  2. Destination needs a drain.

What is the expected behavior? Why is that the expected behavior?

Stream starts flowing and drains through the writable:

write 1000
write 100
src buffer 0

What do you see instead?

Stream deadlocks:

write 1000
src buffer 100

Additional information

#36563 fixes a another pipe deadlock introduced in #35348. This was introduced in v16.0 and backported to v14 in v14.17. However, the fix is not complete, and pipe() can still deadlock as demonstrated!

I expect the issue is caused by the conditional calling of pause() here:

if (dest.writableNeedDrain === true) {
if (state.flowing) {
pause();
}

To verify this, I tried to call src.resume() right before the call to src.pipe(). This makes state.flowing === true, and ensures pause() is called.

As far I can tell, this issue is present in all active and maintenance release lines, and was introduced into v14 during the LTS cycle. This will likely also affect the current readable-stream.

Until this is fixed, or for anyone still using v14 (and possibly v16 depending on how critical a fix is regarded), a workaround is to always call stream.resume() before calling stream.pipe().

@kanongil
Copy link
Contributor Author

kanongil commented Jul 6, 2023

FYI, this is not just a theoretical issue, but can occur in real code (which is why I found it). For my code, the issue shows itself when a http download is "fast".

@lpinca lpinca added the stream Issues and PRs related to the stream subsystem. label Jul 6, 2023
@kanongil
Copy link
Contributor Author

kanongil commented Jul 7, 2023

Hmm, I tried to workaround this issue by using stream.pipeline(), but this deadlocks as well. I guess that makes sense, since it seems to use stream.pipe() as part of the processing.

@benjamingr
Copy link
Member

@nodejs/streams

ronag added a commit to nxtedition/node that referenced this issue Jul 7, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
ronag added a commit to nxtedition/node that referenced this issue Jul 7, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
ronag added a commit to nxtedition/node that referenced this issue Jul 7, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
ronag added a commit to nxtedition/node that referenced this issue Jul 7, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
ronag added a commit to nxtedition/node that referenced this issue Jul 7, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
ronag added a commit to nxtedition/node that referenced this issue Jul 10, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
PR-URL: nodejs#48691
nodejs-github-bot pushed a commit that referenced this issue Jul 12, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: #48666
PR-URL: #48691
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
juanarbol pushed a commit that referenced this issue Jul 13, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: #48666
PR-URL: #48691
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Ceres6 pushed a commit to Ceres6/node that referenced this issue Aug 14, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
PR-URL: nodejs#48691
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Ceres6 pushed a commit to Ceres6/node that referenced this issue Aug 14, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
PR-URL: nodejs#48691
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
kanongil pushed a commit to kanongil/node-1 that referenced this issue Aug 25, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: nodejs#48666
PR-URL: nodejs#48691
Backport-PR-URL: nodejs#49323
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
ruyadorno pushed a commit that referenced this issue Sep 8, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: #48666
PR-URL: #48691
Backport-PR-URL: #49323
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
ruyadorno pushed a commit that referenced this issue Sep 13, 2023
When piping a paused Readable to a full Writable we didn't
register a drain listener which cause the src to never
resume.

Refs: #48666
PR-URL: #48691
Backport-PR-URL: #49323
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
@jakecastelli
Copy link
Member

This issue has been fixed in v20.5.

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

No branches or pull requests

4 participants