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

EPIPE when writing to newly created child process's stdin #40085

Closed
ehmicky opened this issue Sep 11, 2021 · 7 comments
Closed

EPIPE when writing to newly created child process's stdin #40085

ehmicky opened this issue Sep 11, 2021 · 7 comments
Labels
child_process Issues and PRs related to the child_process subsystem.

Comments

@ehmicky
Copy link

ehmicky commented Sep 11, 2021

Version

16.7.0 (changelog)

Platform

Linux ether-laptop 5.11.0-34-generic #36-Ubuntu SMP Thu Aug 26 19:22:09 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Subsystem

child_process

What steps will reproduce the bug?

import { spawn } from 'child_process'

const childProcess = spawn('echo')
childProcess.stdin.end('test')

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

With Node <=16.6.0, this creates no error.

Also, this only produces an error with specific binaries. echo does produce the error, but cat or node --version do not.

Finally, this seems to happen on macOS and Linux, but not on Windows.

What is the expected behavior?

No error should be produced.

Also, test should be written to the child process's stdin. Some modules like Execa rely on being able to pass a string or buffer to a child process's stdin (upstream issue for Execa: sindresorhus/execa#474).

What do you see instead?

With Node >=16.7.0, this produces:

node:events:371
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:164:15)
    at writeGeneric (node:internal/stream_base_commons:155:3)
    at Socket._writeGeneric (node:net:780:11)
    at Socket._write (node:net:792:8)
    at writeOrBuffer (node:internal/streams/writable:389:12)
    at _write (node:internal/streams/writable:330:10)
    at Socket.Writable.end (node:internal/streams/writable:609:17)
    at Socket.end (node:net:594:31)
    at file:///myfile.js:4:20
    at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
Emitted 'error' event on Socket instance at:
    at emitErrorNT (node:internal/streams/destroy:157:8)
    at emitErrorCloseNT (node:internal/streams/destroy:122:3)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}
@Mesteery Mesteery added the child_process Issues and PRs related to the child_process subsystem. label Sep 11, 2021
@blattersturm
Copy link

It should be noted that this code is already resulting in this error on v16.6.0 and even v16.2.0, at least on Alpine Linux. The same goes for v14.17.6 from the Alpine repositories, as well as the same 14.x version from the Nodesource repositories on Ubuntu.

@blattersturm
Copy link

In fact, this same behavior shows on v12.13.0 as well.

@santigimeno
Copy link
Member

Is this really bug? The echo process is actually gone when you actually try to write into its stdin, thus the EPIPE error.

@ehmicky
Copy link
Author

ehmicky commented Sep 23, 2021

Thanks @blattersturm and @santigimeno, I believe you are both correct.

I tried reproducing my original problem and it now reproduces with much older Node.js versions as mentioned by @blattersturm.

This seems to be a timing issue as pointed out by @santigimeno. For example, I have found that the following command works approximately half of the times on my machine (to reproduce, you need to adjust the size):

spawn('bash', ['-c', 'echo ' + 'a'.repeat(57367)], { stdio: ['pipe', 'ignore', 'inherit'] })

However, even if the child process is in fact gone (which is most likely to be the case), the child_process instance does not indicate it. Also, the stdin attributes and events indicate it as writable. For example:

import { spawn } from 'child_process'

const childProcess = spawn('echo')

console.log('childProcess pid', childProcess.pid)
childProcess.on('spawn', () => {
  console.log('childProcess spawn')
})
childProcess.on('exit', (exitCode, signal) => {
  console.log('childProcess exit', exitCode, signal)
})
childProcess.on('close', (exitCode, signal) => {
  console.log('childProcess close', exitCode, signal)
})
childProcess.on('error', (error) => {
  console.log('childProcess error', error)
})

childProcess.stdin.on('error', (error) => {
  console.log('stdin error', error)
})

console.log('stdin writable', childProcess.stdin.writable)
console.log('stdin writableEnded', childProcess.stdin.writableEnded)
console.log('stdin writableFinished', childProcess.stdin.writableFinished)
console.log('stdin destroyed', childProcess.stdin.destroyed)

console.log('before stdin write')
childProcess.stdin.write('test')

Results in:

childProcess pid 35573
stdin writable true
stdin writableEnded false
stdin writableFinished false
stdin destroyed false
before stdin write
childProcess spawn
stdin error Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:164:15)
    at writeGeneric (node:internal/stream_base_commons:155:3)
    at Socket._writeGeneric (node:net:780:11)
    at Socket._write (node:net:792:8)
    at writeOrBuffer (node:internal/streams/writable:389:12)
    at _write (node:internal/streams/writable:330:10)
    at Socket.Writable.write (node:internal/streams/writable:334:10)
    at file:///home/ether/Desktop/a.js:29:20
    at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
    at async Loader.import (node:internal/modules/esm/loader:178:24) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}
childProcess exit 0 null
childProcess close 0 null

Are those attributes and events correct considering stdin.write() fails?

As a consequence, it becomes impossible to do the following safely: write to a child process's stdin as it starts. This is very useful and used by execa's input feature, for example.

The best ways I can think of to solve this would be:

  • To ignore EPIPE errors on stdin until child process has emitted the spawn event. However, this is bad as it might ignore actual unrelated errors.
  • To create a stream from the string/buffer input and pass this to the stdio option of child_process.spawn().

Is there a better way to do this?

@ehmicky ehmicky changed the title EPIPE when writing to child process's stdin since Node 16.7.0 EPIPE when writing to child process's stdin Sep 23, 2021
@ehmicky ehmicky changed the title EPIPE when writing to child process's stdin EPIPE when writing to newly created child process's stdin Sep 23, 2021
@sidwebworks
Copy link
Contributor

Facing the same issue on v16.18.0

 readable.pipe(child.stdin, { end: true }) // pipeline API also breaks
Error: write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:94:16) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

@bnoordhuis
Copy link
Member

I'll go ahead and close this because I believe the consensus is that this isn't a bug (and I agree.) The child process object and the stdio streams emit various events that you can observe to know when it's safe to read and write.

@bnoordhuis bnoordhuis closed this as not planned Won't fix, can't repro, duplicate, stale May 7, 2023
@xmedeko
Copy link

xmedeko commented Jan 12, 2024

@bnoordhuis There is no flag or event which may be observed to write to stdin safely, see discussion at #48786

TL;DR see the comment #48786 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
child_process Issues and PRs related to the child_process subsystem.
Projects
None yet
Development

No branches or pull requests

7 participants