Repository navigation
stream.pipeline abruptly kill the node process #48406
Description
Activity
- changed the title
[-]stream.pipeline abruptly kill the node process without any signalling[/-][+]stream.pipeline abruptly kill the node process[/+]on Jun 9, 2023 You got into Async and Await hell. In function pipeLineStream() you should not use 'await'. Instead use .then() as it is returning promise. like:
`const pipelineStream = async (rs) => {
const ws = createWriteStream(OUTPUT_FILENAME);pipeline(rs, ws).then(()=>{
console.log("finished piping");
})};`
This will work.
@rohith-bot The standard output seems to be "half" working, but the file
foobar.txtis still in malformed state.--- node version: v18.16.0 --- using `stream.pipeline(rs,ws)` --- program finished successfullyNotice that
finished pipingdidn't get printed, but--- program finished successfullydidAdditionally, can you explain why some earlier versions of node (
v16.20.0,v18.15.0) can run this code successfully usingawait pipeline(rs, ws), butv18.16.0or later can't ?And also, why can't I
awaitthe promisifiedsteam.pipeline? even the example from the nodejs itself (link) do useawait pipeline(...)- addedstreamIssues and PRs related to Node.js streams.Issues and PRs related to Node.js streams.
on Jun 10, 2023 have you tried using a buffer
@rohith-bot This issue isn't about how to circumvent the problem, as I've already found the viable workaround without the need to use
Buffer(the workaround is in the functionmanuallyPipeStream).It is about pointing out the valid usecase of
stream.pipelinethat leads to unexpected behaviors, even in the same major versions of nodejs (v18.15.0is ok, butv18.16.0isn't).@nodejs/streams
Can you make a more minimal sample?
@ronag From 50 to 18 LoC, only focus on the bug, node
v18.16.0or later failsimport { createWriteStream } from "fs"; import { Readable, promises } from "stream"; const generateContent = async (rs) => { rs.push("start\n"); for (let i = 0; i < 1024; i++) rs.push("11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000\n"); rs.push("finished\n"); rs.push(null); }; const main = async () => { const [rs, ws] = [new Readable(), createWriteStream("foobar.txt")]; await Promise.all([generateContent(rs), promises.pipeline(rs, ws)]); console.log("--- program finished successfully"); }; main().catch((e) => console.error("*** ERR:", e));
I think this is normal (unfortunately) and expacted. Promises do not keep the event loop alive and I think there is a race condition where there are only promises waiting to be processed.
To fix this, you should use ESM and Top-Level Await and await your
mainfunction.-
It is already ESM from the beginning (notice the
importstatements), and it need to be run in.mjsfile -
I don't get the "Promises do not keep the event loop alive" part, if that's the case then this code snippet would exit immediately without waiting for 5 seconds, whether it's ESM or not.
const main = async () => { await new Promise((resolve) => setTimeout(resolve, 5000)); console.log("finished waiting"); }; main();
But for the sake of conversation and progress, as you suggest, I did change the last line into
await main().catch((e) => console.error("*** ERR:", e));
And nothing changed.
foobar.txtstill malformed.About the race conditions part, possibly, but certainly not the Promises. Otherwise it would affect all versions of node that support Promise.
I re-checked the code again, and it's clearly that I did
awaitall Promises that need to be done, which is only 1 place.// ... await Promise.all([generateContent(rs), promises.pipeline(rs, ws)]); // ...
-
I just ran the sample on
v18.13.0and I don't see any issue?v18.16.0seems to have it.@ronag It affects
v18.16.0or later,v18.15.0or earlier are ok. You can check the required conditions in the original post.Reacted by Robert NagyLet me re-phrase the required conditions that this issue affects again, using minimal snippet.
node >= v18.16.0: affected by itnode <= v18.15.0: NOT affected by it
Yea, I see the problem.
- added a commit that references this issue
on Jun 24, 2023 - added a commit that references this issue
on Jul 3, 2023 - added 2 commits that reference this issue
on Aug 14, 2023 - added 2 commits that reference this issue
on Sep 10, 2023
Version
v18.16.0
Platform
Linux 6.3.6-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Mon, 05 Jun 2023 15:12:42 +0000 x86_64 GNU/Linux
Subsystem
stream
What steps will reproduce the bug?
This is the snippet that generate Readable stream by pushing
start,finished, and 1024 UUID instances in between, and then using that to pipe to file Writable stream namedfoobar.txtThere are two code paths in this snippet
node pipeline.mjswill usestream.pipeline(rs,ws)to stream the filepipelineStreamnode pipeline.mjs mwill users.pipe(ws)+Promiseto stream the filemanuallyPipeStreamHow often does it reproduce? Is there a required condition?
I've tested the code against
v16.20.0,v18.15.0,v18.16.0,v20.3.0, in Arch Linux and macOS 12OSes doesn't seems to be the factor of this problem.
The problem seems to only exist when all of these conditions are met:
v18.16.0orv20.3.0node pipeline.mjs, to usestream.pipeline(rs,ws)code pathWhat is the expected behavior? Why is that the expected behavior?
When running the code successfully,
foobar.txtwith the size of 37 KiB, with properstart,finishedand UUIDs in betweenWhat do you see instead?
The node process is abnormally killed without any error/exception thrown and exit code is just
0File
foobar.txtbecome malformed:startand 443 UUIDs, withoutfinished16384Additional information
Regardless of node versions and OSes, running the code using flag:
node pipeline.mjs m, to users.pipe(ws)+Promisecode path, do always produce the correct results.Minimal snippet that has one code path, and only focus on the issue