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

Feature request: ChildProcess 'spawn' event #35288

Closed
zenflow opened this issue Sep 21, 2020 · 11 comments
Closed

Feature request: ChildProcess 'spawn' event #35288

zenflow opened this issue Sep 21, 2020 · 11 comments
Labels
child_process Issues and PRs related to the child_process subsystem. feature request Issues that request new features to be added to Node.js. good first issue Issues that are suitable for first-time contributors.

Comments

@zenflow
Copy link
Contributor

zenflow commented Sep 21, 2020

Is your feature request related to a problem? Please describe.

After calling child_process.spawn, I'd like to know when the child process has successfully spawned and there's no longer the possibility of an 'error' event from failing to spawn (i.e. error type # 1 in the docs for that 'error' event, e.g. EPERM, ENOENT).

Currently I just wait 100 milliseconds (after calling child_process.spawn), and if the child process hasn't emitted an 'error' event by that time, I assume it spawned successfully. My code looks something like this:

const { spawn } = require('child_process');
const { promisify } = require('util');
const { once } = require('events');
const timeout = promisify(setTimeout);

async function doSomethingWithChildProcess(){
  const subprocess = spawn(...spawnArgs));
  await Promise.race([
    timeout(100),
    once(subprocess, 'error').then(([error]) => Promise.reject(error))
  ]);
  // now do something with the running child process...
}

This seems to work, but I'm not sure how reliable it is, and anyways, it is certainly a hack.
I'm wondering if there could be a better (more correct) way..

Describe the solution you'd like

Is there some point of execution in Node where we know there was no error spawning the child process?

If so, we could introduce a new 'spawn' event for the ChildProcess class, to be emitted at that point in execution?

That would remove the need for the unreliable hack I described above.

It would also work nicely with Node.js's events.once function. For example, the code from above could be updated like this:

 const { spawn } = require('child_process');
-const { promisify } = require('util');
 const { once } = require('events');
-const timeout = promisify(setTimeout);

 async function doSomethingWithChildProcess(){
   const subprocess = spawn(...spawnArgs);
-  await Promise.race([
-    timeout(100),
-    once(subprocess, 'error').then(([error]) => Promise.reject(error))
-  ]);
+  await once(subprocess, 'spawn');
   // now do something with the running child process...
 }

Describe alternatives you've considered

Just the hack I described above (of expecting any error spawning to happen within 100 milliseconds of calling child_process.spawn).

I can't think of any other possible solutions at this time.

@bnoordhuis bnoordhuis added child_process Issues and PRs related to the child_process subsystem. feature request Issues that request new features to be added to Node.js. labels Sep 22, 2020
@bnoordhuis
Copy link
Member

It's not hard to implement, see diff. Feel free to steal for a PR, no attribution needed. Needs docs and tests, however.

diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js
index 31d9f02df3..5672f3429f 100644
--- a/lib/internal/child_process.js
+++ b/lib/internal/child_process.js
@@ -400,6 +400,8 @@ ChildProcess.prototype.spawn = function(options) {
     this._handle.close();
     this._handle = null;
     throw errnoException(err, 'spawn');
+  } else {
+    process.nextTick(onSpawnNT, this);
   }
 
   this.pid = this._handle.pid;
@@ -465,6 +467,11 @@ function onErrorNT(self, err) {
 }
 
 
+function onSpawnNT(self) {
+  self.emit('spawn');
+}
+
+
 ChildProcess.prototype.kill = function(sig) {
 
   const signal = sig === 0 ? sig :

Apropos this:

Is there some point of execution in Node where we know there was no error spawning the child process?

Yes, but there's a subtlety that is likely to trip up some people (mostly the magical thinker kind): while bash bob spawns successfully, bash then fails to spawn bob. You'll still get a spawn event.

That caveat also applies to { shell: true } and it seems to be a perennial source of confusion to novice users who expect it to DWIM rather than do what the documentation says it does.

@bnoordhuis bnoordhuis added the good first issue Issues that are suitable for first-time contributors. label Sep 22, 2020
@kaushik94
Copy link

@bnoordhuis can I take this up?

@zenflow
Copy link
Contributor Author

zenflow commented Sep 22, 2020

I'd also like to claim this issue 😄 @kaushik94 Would that be ok with you?

@kaushik94
Copy link

@zenflow I'm sorry, I thought you were a maintainer. Sure you claim the issue as you raised the issue. Thanks, looking forward!

@zenflow
Copy link
Contributor Author

zenflow commented Sep 22, 2020

Thanks @kaushik94 😃

zenflow added a commit to zenflow/node that referenced this issue Oct 28, 2020
The new event signals that the subprocess has spawned successfully and
no 'error' event will be emitted from failing to spawn.

Fixes: nodejs#35288
targos pushed a commit that referenced this issue Nov 3, 2020
The new event signals that the subprocess has spawned successfully and
no 'error' event will be emitted from failing to spawn.

Fixes: #35288
PR-URL: #35369
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
targos pushed a commit that referenced this issue Mar 3, 2021
The new event signals that the subprocess has spawned successfully and
no 'error' event will be emitted from failing to spawn.

Fixes: #35288
PR-URL: #35369
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
@alexp1917
Copy link

this this event never fire sometimes? the docs say "if emitted" but don't specify why it wouldn't be. It is sometimes not emitted when things to smoothly (on windows, i am doing var child = spawn('yarn.cmd'); then listening for all events right away - and seeing an exit and close but not a spawn event. thanks

zenflow added a commit to zenflow/node that referenced this issue Mar 20, 2021
Making this clarification in response to a comment on GitHub:
nodejs#35288 (comment)
@zenflow
Copy link
Contributor Author

zenflow commented Mar 20, 2021

@alexp1917 Yeah I agree the docs could be more explicit about the case where this event does not fire. Please see my PR and let me know if it makes things perfectly clear: https://github.com/nodejs/node/pull/37833/files

@alexp1917

  • Are you always using a supported Node.js version? This event was only added as of v15.1.0.
  • Is the 'error' event not firing in these cases where 'spawn' is not fired? Either a 'spawn' event or an 'error' event should fire, every time.

If the answer to both of these questions is "yes", then I think we may have a bug in this feature..

@alexp1917
Copy link

alexp1917 commented Mar 20, 2021 via email

jasnell pushed a commit that referenced this issue Mar 22, 2021
Making this clarification in response to a comment on GitHub:
#35288 (comment)

PR-URL: #37833
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
ruyadorno pushed a commit that referenced this issue Mar 24, 2021
Making this clarification in response to a comment on GitHub:
#35288 (comment)

PR-URL: #37833
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MylesBorins pushed a commit that referenced this issue Apr 6, 2021
The new event signals that the subprocess has spawned successfully and
no 'error' event will be emitted from failing to spawn.

Fixes: #35288
PR-URL: #35369
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
targos pushed a commit that referenced this issue May 1, 2021
Making this clarification in response to a comment on GitHub:
#35288 (comment)

PR-URL: #37833
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@sylann
Copy link

sylann commented Jun 10, 2021

I also have a case of missing 'spawn' event but only in certain circonstances.

With node v15 on macos, it works
With node v16 on a windows vm on this same mac, it works too.

However, if I run the spawn command from an electron main process on the windows vm,
then I never get a "spawn" event even though the child process has started successfully.
I never get and "error" event either.

So I ended up adding a terrible setTimeout to resolve automatically if no error occured.

Side notes, I have tried spawning .exe files and .bat files or linux programs such as 'ls' and it doesn't change what I've said above.

@QNimbus

This comment was marked as resolved.

@QNimbus
Copy link

QNimbus commented May 23, 2022

Note: I accidentally hid my previous comment - this is an unhidden copy of that comment

public spawn = (): Promise<void> => {
    return new Promise((resolve, reject) => {
      const cp = spawn(cmd, [], {
        cwd: process.cwd(),
        env: process.env,
        stdio: ['pipe', 'pipe', process.stderr ],        
      });
  
      // Register listeners once the child has spawned
      cp.on('spawn', function () {
        // Resolve promise
        return resolve();
      });

      cp.on('error', (err: Error) => {
        // Reject promise
        return reject(err);
      });
    });
  };

I seem to experience this issue too - the 'error' event never fires and my child process seems to spawn succesfully (judging by the output on my console) but the 'spawn' event also never fires. Or maybe it fires before the event handler is registered, but either way - the above code does not work for me.

I've managed to 'fix' this issue with a bit of a hack - With the absence of the 'error' event getting thrown, I'm running a while-loop until cp.connected == true after which I return the resolved promise:

while (!this._childProcess.connected); return resolve();

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. feature request Issues that request new features to be added to Node.js. good first issue Issues that are suitable for first-time contributors.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants