nodejs / nodejs/node

regression: child_process stdin pipe close event not emitted

Aperta
#25,131 11 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

child_process confirmed-bug net stream
Lingua principale
JavaScript
Stelle
122k
Fork
37.3k
Merge medio
4g 2h
PR unite (30g)
283

Descrizione

Summary
  • Version: v8.12.0 and newer, all v10.x and all v11.x
  • Platform: Linux and macOS amd64
  • Subsystem: child_process

Since #18701 Node.js doesn't emit a close event when a child process closes its stdin pipe. The close is only detected if the parent tries to write to the child's stdin and receives an EPIPE.

The original behaviour is preferable because it allows immediate detection of the close event, rather than waiting for a failed write.

Reproducer

This works as expected in v8.11.4 and fails in all newer versions of v8.x, as well as all v10.x and all v11.x.

const {spawn} = require('child_process');

const cp = spawn(
    'node', [
        '-e',
        'fs.closeSync(0); setTimeout(() => {}, 2000)'
    ],
    {stdio: ['pipe', 'inherit', 'inherit']}
)

setTimeout(() => {
    console.log('BUGGY! stdin close event was not emitted')
    process.exit(1);
}, 1000);

cp.stdin.on('close', () => {
    console.log('Ok!')
    process.exit(0);
});
Problem detail

As far as I can tell the only way the close event can be emitted is if the Socket constructor calls this.read(0). This triggers onStreamRead() to be called, which does stream.push(null) and eventually results in the close event. This is a little weird because stdin isn't a readable stream :)

In Node 8.11.4 this worked because child_process.js:createSocket() called the Socket constructor with options.readable === undefined. So the Socket constructor sees that options.readable !== false and runs this.read(0)

In PR #18701 createSocket() was changed to call the Socket constructor with options.readable === true. This stops this.read(0) from being called and the close event is not emitted.

Hacky solution
--- a/lib/internal/child_process.js
+++ b/lib/internal/child_process.js
@@ -286,3 +286,3 @@ function flushStdio(subprocess) {
 function createSocket(pipe, readable) {
-  return net.Socket({ handle: pipe, readable, writable: !readable });
+  return net.Socket({ handle: pipe, readable, writable: !readable, childProcess: true });
 }
--- a/lib/net.js
+++ b/lib/net.js
@@ -315,3 +315,5 @@ function Socket(options) {
   // buffer.  if not, then this will happen when we connect
-  if (this._handle && options.readable !== false) {
+  if (options.childProcess) {
+    this.read(0);
+  } else if (this._handle && options.readable !== false) {
     if (options.pauseOnCreate) {

This passes the full test suite with a minor change to test-pipewrap.js. I haven't raised a PR because I'm sure somebody could think of a better solution. If someone can point me in the right direction I'm happy to try and implement it.

Thanks :)

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con lib/internal/child_process.js e lib/net.js, quindi confronta la modifica alla creazione del socket di #18701. Esegui il reproducer child_process fornito e ispeziona test-pipewrap.js, inclusa la sua modifica minore menzionata nell’issue. Il lavoro è terminato quando stdin emette close tempestivamente quando il processo figlio chiude la pipe e i test pertinenti hanno esito positivo.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
javascript, node.js
Ambito
backend, operating-systems
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Tranquilla
Chiarezza
Specificata chiaramente
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.