You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Piping process.stdin to child.stdin leaves behind an open handle #32291
After entering some input, the child process exits and done is printed. However the main process does not exit (although it should). It looks like process.stdin has a left handle. After entering newline again on stdin, the process now exits.
I think ultimately the issue here is that once we’ve told a stream to start reading, we have no way of telling it to stop until it does read some data. We have a hack specifically for process.stdin.pause() in place, but that won’t work for other streams or, apparently, .unpipe().
@ronag @nodejs/streams Any ideas? Would it be possible to figure out when a stream is no longer actively being consumed and e.g. emit a event in that case that stops an in-progress _read()?
Any ideas? Would it be possible to figure out when a stream is no longer actively being consumed and e.g. emit a event in that case that stops an in-progress _read()?
We do not expose readStop() to the end users: stopping/aborting an ongoing request is something we have no agreed way to do. The closer we got is the stream.destroy() mechanism.
For legacy reasons, pipe() does not destroy streams. Therefore this is not a bug and it works as expected.
The "correct" implementation is:
const{ spawn }=require('child_process')const{ pipeline }=require('stream')constchild=spawn('./foo')// pipeline calls destroy for youpipeline(process.stdin,child.stdin,(err)=>{console.log('done',err)})
Reacted by Robert Nagy
Reacted by Anna Henningsen and The Half Blood Prince
@mcollina I’m sorry but I think that answer misses the point here.
We do not expose readStop() to the end users: stopping/aborting an ongoing request is something we have no agreed way to do. The closer we got is the stream.destroy() mechanism.
Yes. I am asking for a new mechanism to be introduced for this, essentially.
For legacy reasons, pipe() does not destroy streams. Therefore this is not a bug and it works as expected.
That may be helpful for the issue that OP is running into, but it assumes that you always want to destroy a stream when you are unpiping it, and that’s definitely not true. What if stdin were to be piped into another destination after the child process ended?
This problem also arises even without the use of pipes:
The property of _handle is managed by net.Socket (and equivalents, e.g. tty). Are you asking for a callback that is called when pause or unpipe happen and implement it into net.Socket?
Original OP here: the short version is that I performed one pipe with a corresponding unpipe and expected that to correctly dereference anything attached to the stream such that the event loop would be free to exit. Basically, shouldn't there be a reference count of some sort on the internal resources that gets decremented (freed) on unpipe?
I would like to add that destroying the stream is not always an option in cases where the function does not know whether the stream will be re-used (in which case it should not be destroyed) or not (in which case it should not leave a handle open, keeping the parent process running).
Reacted by James Sumners, Sindre Sorhus and Levi Roberts
Do you maybe have a concrete example of this? Shouldn't it be up to the "root" consumer/app then to ensure that resources are released/destroyed? If resources are not properly cleaned up then it should keep running, no?
This seems to be a problem that spawning a child process that uses the parent stdin requires destroying it to do a proper cleanup, instead of just reverting the pipe().
Hm, this is not my area of expertise but I would expect any open handles/references to be closed on 'exit' and data just to be buffered in memory on the stdin stream (i.e. it should just have buffered data without any handle reference). What keeps the process alive in this case?
Do you maybe have a concrete example of this? Shouldn't it be up to the "root" consumer/app then to ensure that resources are released/destroyed? If resources are not properly cleaned up then it should keep running, no?
I'm writing essentially a script runner and I want to run multiple processes in a row. In this case, I'm basically converting shell scripts to Node.js:
so I'm trying to write an execute command that works for this usecase. This is a usecase where I don't think the "root" application should always have to be like
main().then(()=>{process.stdin.destroy();});
which I think is the implication here. It's part of the contract of execute that stdio gets forwarded to the subprocess during the Promise execution and gets returned after resolution, so it just feels gross that there's still more cleanup to do.
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
Activity
mscdex commentedon Mar 15, 2020
You could replace
process.stdin.unpipe(child.stdin)withprocess.stdin.destroy().addaleax commentedon Mar 15, 2020
@mscdex That’s true, but it still seems to me like when we’re not reading from the only stream in use, that that should stop the event loop?
ehmicky commentedon Mar 15, 2020
Thanks @mscdex, this does make the process exit properly.
But as @addaleax points out, this still indicates an underlying issue: the main process should exit without having to destroy
process.stdin.addaleax commentedon Mar 16, 2020
I think ultimately the issue here is that once we’ve told a stream to start reading, we have no way of telling it to stop until it does read some data. We have a hack specifically for
process.stdin.pause()in place, but that won’t work for other streams or, apparently,.unpipe().@ronag @nodejs/streams Any ideas? Would it be possible to figure out when a stream is no longer actively being consumed and e.g. emit a event in that case that stops an in-progress
_read()?mcollina commentedon Mar 17, 2020
We do not expose
readStop()to the end users: stopping/aborting an ongoing request is something we have no agreed way to do. The closer we got is thestream.destroy()mechanism.For legacy reasons,
pipe()does not destroy streams. Therefore this is not a bug and it works as expected.The "correct" implementation is:
addaleax commentedon Mar 17, 2020
@mcollina I’m sorry but I think that answer misses the point here.
Yes. I am asking for a new mechanism to be introduced for this, essentially.
That may be helpful for the issue that OP is running into, but it assumes that you always want to destroy a stream when you are unpiping it, and that’s definitely not true. What if stdin were to be piped into another destination after the child process ended?
This problem also arises even without the use of pipes:
After the first chunk, there is nothing in the process that should keep the event loop alive, but the process does not stop trying to read from
stdin.mcollina commentedon Mar 17, 2020
I understand now.
The property of
_handleis managed bynet.Socket(and equivalents, e.g.tty). Are you asking for a callback that is called whenpauseorunpipehappen and implement it intonet.Socket?addaleax commentedon Mar 17, 2020
@mcollina Yes,
pauseorunpipeor when reading stops in another way such as removing alldata/readablelisteners 👍mcollina commentedon Mar 17, 2020
I think that's possible. It means adding some more intelligence to:
node/lib/_stream_readable.js
Lines 876 to 924 in 40b559a
jsumners commentedon Mar 17, 2020
Original OP here: the short version is that I performed one
pipewith a correspondingunpipeand expected that to correctly dereference anything attached to the stream such that the event loop would be free to exit. Basically, shouldn't there be a reference count of some sort on the internal resources that gets decremented (freed) onunpipe?ehmicky commentedon Mar 17, 2020
I would like to add that destroying the stream is not always an option in cases where the function does not know whether the stream will be re-used (in which case it should not be destroyed) or not (in which case it should not leave a handle open, keeping the parent process running).
ronag commentedon Mar 17, 2020
Do you maybe have a concrete example of this? Shouldn't it be up to the "root" consumer/app then to ensure that resources are released/destroyed? If resources are not properly cleaned up then it should keep running, no?
ehmicky commentedon Mar 17, 2020
Yes you're right. What I meant was more along the following lines. In an example like:
This seems to be a problem that spawning a child process that uses the parent
stdinrequires destroying it to do a proper cleanup, instead of just reverting thepipe().ronag commentedon Mar 17, 2020
Hm, this is not my area of expertise but I would expect any open handles/references to be closed on
'exit'and data just to be buffered in memory on thestdinstream (i.e. it should just have buffered data without any handle reference). What keeps the process alive in this case?rivertam commentedon Apr 21, 2021
I'm writing essentially a script runner and I want to run multiple processes in a row. In this case, I'm basically converting shell scripts to Node.js:
becomes
so I'm trying to write an
executecommand that works for this usecase. This is a usecase where I don't think the "root" application should always have to be likewhich I think is the implication here. It's part of the contract of
executethat stdio gets forwarded to the subprocess during the Promise execution and gets returned after resolution, so it just feels gross that there's still more cleanup to do.robsonos commentedon Nov 13, 2022
Hi all, any updates on this?
I have a similar issue where after I unpipe a child
stream.transformthe parentstreambecomes unresponsivegithub-actions commentedon Jun 27, 2026
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
jsumners commentedon Jun 27, 2026
Pretty sure it never got solved, but 🤷♂️
ehmicky commentedon Jun 27, 2026
Re-running the original examples, it seems like this bug was solved in Node
13.12.0.