pushkin
pushkin

Reputation: 10199

Synchronously checking if a child process failed to spawn in Node?

I spawn a child process. After I spawn it, I write to the outgoing stream, so I want to synchronously detect if it failed to spawn before doing that (otherwise I get a write EPIPE error).

let proc = cp.spawn("path/file.exe");
proc.on("error", (err) => { throw err; });
proc.stdin.write(...);

Now if I put the write on a setTimeout(() => { proc.stdin.write(...); }, 0), the error handler will get hit first, and we'll crash before doing the write, but that's a little ugly.

What I have found is that after cp.spawn returns, we can check the proc.pid property, and if it's a number, we know that the process spawned. If we give it the path of a nonexistent executable, proc.pid is undefined, so it seems that we can synchronously check if the process failed to spawn, and we don't need to rely on the asynchronous error callback (though the callback catches other errors).

Node's documentation has a section on checking for a failed spawn, but it suggests the asynchronous method:

const subprocess = spawn('bad_command');

subprocess.on('error', (err) => {
  console.log('Failed to start subprocess.');
});

Nowhere does it mention that checking for an undefined proc.pid is a suitable synchronous alternative for checking for a failed spawn, which me leads to wonder if this a robust and recommended way of doing it. Can anyone comment on this?

Upvotes: 1

Views: 2222

Answers (1)

pushkin
pushkin

Reputation: 10199

Chatted with the folks at Node. Quoting from here.

Is there anyway to detect if a process has successfully spawned?

Sadly, no, not in the general case.

I have no guarantee that the process successfully spawned until I start actually writing to its streams and get errors.

Yup, that’s correct. libuv does a best-effort thing here, but it’s impossible to get a guaranteed result without resorting to e.g. polling techniques that look up the process pid and what that process is currently doing.

I have found that I can check proc.pid right after spawning, and if I pass in a faulty path, it's undefined. If I pass in the correct path, it's set to a number, which seems promising. However, someone on SO said that the spawning might still fail after the OS gives back a pid.

Part of the problem here is that there is no clear line for determining what a “failed” spawn attempt means – at what point do you consider a process spawned successfully? There can be errors at any point during process setup.

What exactly is guaranteed after the spawn function returns?

On Unix, that the execve() system call was entered in the child process, and it succeeded in loading the relevant parts of the target executable into memory.

On Windows, a similar thing goes for CreateProcess().

Either way, there are still plenty of reasons why starting a process might fail – missing DLLs/shared libraries, memory exhaustion while running setup code, etc.

At what point in the spawning process are we?

If the spawn() call succeeds, all you know that it’s up to the OS and the spawned process. No code from Node.js/libuv will be executed anymore inside the child.

And can I rely on this proc.pid === undefined technique?

For checking whether the executable could be found, yes, that’s reliable.

If not, how can I at least asynchronously know if the process successfully spawned?

The only thing that comes to my mind would be polling the OS’s way of providing information about the process tree in general.

Why is there no success callback?

When, and most importantly, from where would that callback be called? spawn() finishing means that the OS and the target executable’s code are in charge now, so Node.js can’t make the process do something like saying “I’m good to go”.


Another user recommends:

One thing you could do, is in the child process, write to stdout "spawned successfully", read from stdout in the parent process, and then wait for that message.

const k = cp.spawn('bash');
let stdout = '';

k.on('data', function(d){
  stdout+= String(d);
 if(stdout.match(/spawned successfully/)){
     // do your thing
  }
});

Upvotes: 3

Related Questions