child process not calling close event - node.js

I have the following node.js code:
var testProcess = spawn(item.testCommand, [], {
cwd: process.cwd(),
stdio: ['ignore', process.stdout, process.stderr]
});
testProcess.on('close', function(data) {
console.log('test');
});
waitpid(testProcess.pid);
testProcess.kill();
however the close method never gets calls.
The end result I am looking for is that I spwan a process and the the script waits for that child processs to finish (which waitpid() is doing correctly). I want the output/err of the child process to be display to the screen (which the stdio config is doing correctly). I also want to perform code on the close of the child process which I was going to do in the close event (also tried exit), but it does not fire.
Why is the event not not firing?

http://nodejs.org/api/process.html
Note that just because the name of this function is process.kill, it is really just a signal sender, like the kill system call. The signal sent may do something other than kill the target process.
You can specify the signal while Kill() call.

Looking at waitpid() I found out that it returns an object with the exitCode. I changed my code so that I just perform certain actions based on what the value of the exitCode is.

Related

Kill detached child process in Node

I'm not fully understand why I can't kill a detached process. Can someone help me out?
Server (child process)
const server = spawn(
'npm',
[
'run',
'watch:be',
],
{
detached: true,
},
);
Await for the server to up and running
await waitOn({
resources: [
`https://localhost:${process.env.SERVER_PORT}`,
],
delay: 1000,
timeout: 30000,
});
console.log('server is up and running');
Wait a couple more seconds
await new Promise((resolve, reject): void => {
setTimeout((): void => {
resolve();
}, 2000);
});
console.log('Run test');
Kill the child server
server.kill();
console.log('Shutdown server');
All of these are in the same file.
The child process opened a new terminal window (when it does spawn, which is expected), but doesn't close when kill. Can someone point out what I have done wrong?
server.kill();
As per the node.js documentation, The subprocess.kill() method sends a signal to the child process. When you use the detached option, the node creates a separate process group for the child process and it is not part of the same process anymore.
detached <boolean>: Prepare child to run independently of its parent process
That is the reason kill is not appropriate to use when detached is used.
This has been discussed here:
https://github.com/nodejs/node/issues/2098
As suggested in the above link, you should use process.kill to kill the process (https://nodejs.org/api/process.html#process_process_kill_pid_signal). This should probably work for you:
process.kill(-server.pid)
You said that "The child process opened a new terminal window ..."
Based on this behaviour, it seems that your OS is Windows.
On Windows, setting options.detached to true makes it possible for the child process to continue running after the parent exits. The child will have its own console window. Once enabled for a child process, it cannot be disabled.
source
For process.kill, the second arg is the signal you want to send. By default, this signal is SIGTERM. However, SIGTERM doesn't seem to be supported on Windows, according to the Signal Events section of the Node.js docs.
'SIGTERM' is not supported on Windows, it can be listened on.
Maybe try
process.kill(server.pid, 'SIGHUP')
or
process.kill(server.pid, 'SIGINT')
(This works on macOS but I've not tried it on Windows.)

when does Node spawned child process actually start?

In the documentation for Node's Child Process spawn() function, and in examples I've seen elsewhere, the pattern is to call the spawn() function, and then to set up a bunch of handlers on the returned ChildProcess object. For instance, here is the first example of spawn() given on that documentation page:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
The spawn() function itself is called on the second line. My understanding is that spawn() starts a child process asynchronously. From the documentation:
The child_process.spawn() method spawns a new process using the given
command, with command line arguments in args.
However, the following lines of the script above go on to set up various handlers for the process, so it's assuming that the process hasn't actually started (and potentially finished) between the time spawn() is called on line 2 and the other stuff happens on the subsequent lines. I know JavaScript/Node is single threaded. However, the operating system is not single threaded, and naively one would read that spawn() call to be telling the operating system to spawn the process right now (at which point, with unfortunate timing, the OS could suspend the parent Node process and run/complete the child process before the next line of the Node code is executed).
But it must be that the process doesn't actually get spawned until the current JavaScript function completes (or more generally the current JavaScript event handler that called the current function completes), right?
That seems like a pretty important thing to say. Why doesn't it say that in the Child Process documentation page? Is there some overriding Node principle that makes it unnecessary to say that explicitly?
The spawning of the new process starts immediately (it's handed over to the OS to actually fire up the process and get it going). Starting the new process with .spawn() is asynchronous and non-blocking. So, it will initiate the operation with the OS and immediately return. You might think that that's why it's OK to set up event handlers after it returns (because the process hasn't yet finished starting). Well, yes and no. It likely hasn't yet finished starting the new process, but that isn't the main reason why it's OK.
It's OK, because node.js runs all its events through a single threaded event queue. Thus no events from the newly spawned process can be processed until after your code finishes executing and returns control back to the system. Only then can it process the next event in the event queue and trigger one of the events you are registering handlers for.
Or, said another way, none of the events from the other process are pre-emptive. They won't/can't interrupt your existing Javascript code. So, since you're still running your Javascript code, those events can't get run yet. Instead, they sit in the event queue until your Javascript code finishes and then the interpreter can go get the next event from the event queue and run the callback associated with it. Likewise, that callback runs until it returns back to the interpreter and then the interpreter can get the next event and run its callback and so on...
That's why node.js is called an event-driven system.
As such, it's perfectly fine to do this type of structure:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
None of those data or close events can execute their callbacks until after your code is done and returns control back to the system. So, it's perfectly safe to set up those event handlers like you are. Even if the newly spawned process was running and generating events right away, those events will just sit in the event queue until your Javascript finishes what it is doing (which includes setting up your event handlers).
Now, if you delayed setting up the event handlers until some future tick of the event loop (as shown below) with something like a setTimeout(), then you could miss some events:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
setTimeout(() => {
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
}, 10);
Here you are not setting up the event handlers immediately as part of the same tick of the event loop, but after a short delay. Therefore some events could get processed from the event loop before you install your event handlers and you could miss some of these events. Obviously, you would never do it this way (on purpose), but I just wanted to show that code running on the same tick of the event loop does not have a problem, but code running on some future tick of the event loop could have a problem missing events.
This is to follow up on jfriend00's answer, to explain what it helped me understand, in case it helps someone else. I knew about the event-driven nature of JavaScript/Node. What jfriend00's explanation made clear to me is the idea that an event can happen and Node can be aware that it happened, but it doesn't actually decide which handlers to tell about that event until the next tick. For instance, if the spawn() call fails outright (e.g., command does not exist), Node obviously knows that immediately. My thought was that it would then immediately queue the appropriate handlers to run on the next tick. But what I now understand is that it puts the "raw event" (i.e., the fact that the spawn failed, with whatever details about that) in its queue, and then on the next tick it determines and calls the appropriate handlers. And the same is true for other events like receiving output from the process, etc. The event is saved but the appropriate handlers for the event are only determined when the next tick runs, so handlers assigned on the previous tick, after spawn(), will get called.

Which events should I use for spawning a child process to ensure I always make a callback

I'm using node to wrap an executable and I'm using the spawn event emitter. See the docs here. There are multiple events to subsribe to.
child = spawn("path/to/exe", args)
child.on('close', exitNormally )
child.on('exit', exitNormally )
child.on('error', exitAbnormally )
child.on('disconnect', exitAbnormally )
Should I be subscribing to all of them or is subscribing to close and error enough? I have a callback that I have to execute regardless of whether the outcome is a success or not. The docs for the events are here but it doesn't seem to say explictly say what I'm asking and I want to confirm that my thinking is correct and I don't miss any exits.
The exit event always will be called if your process ends, so I think it will be enough.

Callback when a child_process has successfully processed a signal

I need to verify that a child_process has successfully been killed because I cannot execute the next action if that process is still alive.
var proc = require('child_process');
var prog = proc.spawn('myprog', ['--option', 'value']);
prog.on('data', function(data) {
// Do something
});
Somewhere else in the code I reach to a certain event and on a certain condition I need to kill prog:
prog.kill('SUGHUP');
// Only when the process has successfully been killed execute next
// Code...
Since kill is probably async, I am using q. I would like to use q on kill but kill does not have a callback which is executed when the signal has successfully been processed.
How to do?
Possible idea
If I send a message to process prog and in process prog when receiving the message I kill it? How can tell a process to self-kill?
Wouldn't prog.exec() with the option killsignal and a callback fit your needs ?

Detect when parent process exits

I will have a parent process that is used to handle webserver restarts. It will signal the child to stop listening for new requests, the child will signal the parent that it has stopped listening, then the parent will signal the new child that it can start listening. In this way, we can accomplish less than 100ms down time for a restart of that level (I have a zero-downtime grandchild restart also, but that is not always enough of a restart).
The service manager will kill the parent when it is time for shutdown. How can the child detect that the parent has ended?
The signals are sent using stdin and stdout of the child process. Perhaps I can detect the end of an stdin stream? I am hoping to avoid a polling interval. Also, I would like this to be a really quick detection if possible.
a simpler solution could be by registering for 'disconnect' in the child process
process.on('disconnect', function() {
console.log('parent exited')
process.exit();
});
This answer is just for providing an example of the node-ffi solution that entropo has proposed (above) (as mentioned it will work on linux):
this is the parent process, it is spawning the child and then exit after 5 seconds:
var spawn = require('child_process').spawn;
var node = spawn('node', [__dirname + '/child.js']);
setTimeout(function(){process.exit(0)}, 5000);
this is the child process (located in child.js)
var FFI = require('node-ffi');
var current = new FFI.Library(null, {"prctl": ["int32", ["int32", "uint32"]]})
//1: PR_SET_PDEATHSIG, 15: SIGTERM
var returned = current.prctl(1,15);
process.on('SIGTERM',function(){
//do something interesting
process.exit(1);
});
doNotExit = function (){
return true;
};
setInterval(doNotExit, 500);
without the current.prctl(1,15) the child will run forever even if the parent is dying. Here it will be signaled with a SIGTERM which will be handled gracefully.
Could you just put an exit listener in the parent process that signals the children?
Edit:
You can also use node-ffi (Node Foreign Function Interface) to call ...
prctl(PR_SET_PDEATHSIG, SIGHUP);
... in Linux. ( man 2 prctl )
I start Node.JS from within a native OSX application as a background worker. To make node.js exit when the parent process which consumes node.js stdout dies/exits, I do the following:
// Watch parent exit when it dies
process.stdout.resume();
process.stdout.on('end', function() {
 process.exit();
});
Easy like that, but I'm not exactly sure if it's what you've been asking for ;-)

Resources