end child gracefully when doing Ctrl-C in NodeJS - node.js

I have a architecture with one parent that spawns a child as follow :
this.process = child.spawn(this.cmd, this.args);
this.process.on('exit', (code: number, signal: string) => {
this.exitCode = code;
console.log(`exit code: ${code}`);
});
there is no particular option because i want to keep a link with the child. So, when i press Ctr-C to kill the parent, it catches SIGINT to (1) end the child, (2) exit properly. But SIGINT is also propagated to the child, so the parent cannot end it gracefully. So, is there a way to do so ? maybe by preventing SIGINT to be propagated to the child ? or telling the child to ignore the signal ?
i know that something is possible by adding option detached: true and stdio: 'ignore' when spawning, but i don't want to do that because if the parent dies, i end up with zombies process. Whereas keeping the links ensure that the child is killed if the parent dies unexpectedly. Finally, i want to avoid catching SIGINT in the child as i want to keep it dumb.
EDIT: the parent already have a process.on('SIGINT', () => { ... } and the child is in python.

You can catch exit codes like this:
process.on('SIGINT', () => {
// handle it yourself
})
You can propagate it to children like so:
process.on('SIGINT', () => {
this.child.kill('SIGINT')
})
The child can of course also choose to handle the signal, it is wise to not assume the child process will exit simply because you sent a single signal. You may need to set a timeout and send repeat or different signals to fully kill a process. You will also want to listen to the 'exit' message of the child process to know when its actually killed before continuing.
Documentation
Process Signal Events
Kill Sub-Process With Signal

Related

Node.js child process shutdown delay

I have a node.js process that kicks off a child process (via spawn). When the main process receives a request to shutdown (e.g. SIGTERM) it has to perform some clean-up before the process exits - this can take a few seconds. This clean-up relies on the child process continuing to run - however, the child process is independently responding to the SIGTERM and closing down.
Can I prevent the child process closing until the main process is ready for it to shutdown?
Thanks,
Neil
After spawning child processes in detached mode, you can handle them individually. This can be of use to you: Node child processes: how to intercept signals like SIGINT.
The following assumes your child processes are detached:
process.on('SIGINT', () => {
console.log("Intercepted SIGINT on parent");
// Do your magic here, if you just need to wait for X time, you can use a delay promise:
delay(5000).then(() => {
// Kill/handle your child processes here
process.exit(0); // Then exit the main process
});
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Detach a spawn child process after the start

I start a spawn child process this way:
let process = spawn(apiPath, {
detached: true
})
process.unref()
process.stdout.on('data', data => { /* do something */ })
When I start the process I need to keep it attached because I want to read its output. But just before closing my Node process (the parent) I want to detach all not finished children processes to keep them running in background, but as the documentation say:
When using the detached option to start a long-running process, the process will not stay running in the background after the parent exits unless it is provided with a stdio configuration that is not connected to the parent.
But with the option stdio: 'ignore' I can't read the stdout which is a problem.
I tried to manually close the pipes before to close the parent process but it is unsuccessful:
// Trigger just before the main process end
process.stdin.end()
process.stderr.unpipe()
process.stdout.unpipe()
After many tests I found at least one way to solve this problem : destroying all pipe before to leave the main process.
One tricky point is that the child process have to handle correctly the pipes destroying, if not it could got an error and close anyway. In this example the node child process seems to have no problem with this but it could be different with other scenario.
main.js
const { spawn } = require('child_process')
console.log('Start Main')
let child = spawn('node', ['child.js'], { detached: true })
child.unref() // With this the main process end after fully disconnect the child
child.stdout.on('data', data => {
console.log(`Got data : ${data}`)
})
// In real case should be triggered just before the end of the main process
setTimeout(() => {
console.log('Disconnect the child')
child.stderr.unpipe()
child.stderr.destroy()
child.stdout.unpipe()
child.stdout.destroy()
child.stdin.end()
child.stdin.destroy()
}, 5000)
child.js
console.log('Start Child')
setInterval(function() {
process.stdout.write('hello from child')
}, 1000)
output
Start Main
Got data : Start Child
Got data : hello from child
Got data : hello from child
Got data : hello from child
Got data : hello from child
Disconnect the child

How to execute code after a spawned, detached, child exits from NodeJS

My objective is to have some code execute after a detached, unreferenced, child process is spawned from a NodeJS app. Here is the code that I have:
var child_options = {
cwd : prj
, env : {
PATH: cmd_directory
}
, detatched : true
, stdio : 'ignore'
};
//Spawn a child process with myapp with the options and command line params
child = spawn('myapp', params_array, child_options, function(err, stdout, stderr){
if (err) {
console.log("\t\tProblem executing myapp =>\n\t\t" + err);
} else {
console.log("\t\tLaunched myapp successfully!")
}
});
//Handle the child processes exiting. Maybe send an email?
child.on('exit', function(data) {
fs.writeFile(path.resolve("/Users/me/Desktop/myapp-child.log"), "Finished with child process!");
});
//Let the child process run in its own session without parent
child.unref();
So the function inside the exit handler does not seem to get executed when the child process finishes. Is there any way at all to have code execute after the child process exits even when it's detached and when calling the .unref() method?
Note that if I change the 'stdio' key value in the child_options object from 'ignore' to 'inherit' then the exit handler does execute.
Any ideas?
UPDATE PART 1
So, I still can not figure this one out. I went back to the NodeJS docs on spawn, and noticed the example about spawning "long-running processes". In one example, they redirect the child process' output to files instead of just using 'ignore' for the 'stdio' option. So I changed the 'stdio' key within the child_options object as in the following, but alas I am still not able to execute the code within the 'close' or 'exit' event:
var out_log = fs.openSync(path.resolve(os.tmpdir(), "stdout.log"), 'a'),
err_log = fs.openSync(path.resolve(os.tmpdir(), "stderr.log"), 'a');
var child_options = {
cwd : prj
, env : {
PATH: cmd_directory
}
, detatched : true
, stdio : ['ignore', out_log, err_log]
};
So, the stdout.log file does get the stdout from the child process—so I know it gets redirected. However, the code in the close or exit event still does not execute. Then I thought I would be able to detect when the writing to the out_log file was finished, in which case I would be able to execute code at that point. However, I cannot figure out how to do that. Any suggestions?
You can add listener to 'close' event, e.g. replace 'exit' with 'close'. It worked on my side even with 'ignore' stdio. Also, input parameter in callback is exit code number or null.
According to nodejs documentation difference between exit and close events:
The 'close' event is emitted when the stdio streams of a child process
have been closed. This is distinct from the 'exit' event, since
multiple processes might share the same stdio streams.
Hope it helps.

Nodejs: Send Ctrl+C to a child process on Windows

Hi I am using child_process.spwan to start a child process running a python script on Windows. The script listens on SIGINT to gracefully exits itself. But Windows does not support signals and all node did was simulating. So child_process.kill('SIGINT') on Windows is actually killing the process unconditionally (no graceful exit, python's SIGTERM/SIGINT handler not called). Also writing a ctrl+c character to stdin does not work either.
When I look into Python APIs, I got the CTRL_BREAK_EVENT and CTRL_C_EVENT that can serve the need. I am wondering if node has equivalent platform-specific APIs like these?
Related posts but not working ones:
How to send control C node.js and child_processes
sending crtl+c to a node.js spawned childprocess using stdin.write()?
You can use IPC messages to signal to the child that its time to stop and gracefully terminate. The below approach uses process.on('message') to listen for messages from the parent in the child process & child_process.send() to send messages from the parent to the child.
The below code has a 1 minute timeout set to exit if the child hangs or is taking to long to finish.
py-script-wrapper.js
// Handle messages sent from the Parent
process.on('message', (msg) => {
if (msg.action === 'STOP') {
// Execute Graceful Termination code
process.exit(0); // Exit Process with no Errors
}
});
Parent Process
const cp = require('child_process');
const py = cp.fork('./py-script-wrapper.js');
// On 'SIGINT'
process.on('SIGINT', () => {
// Send a message to the python script
py.send({ action: 'STOP' });
// Now that the child process has gracefully terminated
// exit parent process without error
py.on('exit', (code, signal) => {
process.exit(0);
});
// If the child took too long to exit
// Kill the child, and exit with a failure code
setTimeout(60000, () => {
py.kill();
process.exit(1);
});
});
You could send a 'quit' command via stdin to the Pyhthon process, that worked for me. In Python you need to create a thread which reads from stdin using input, once this returns, you set an event flag. In your main application loop you regularly check whether the event has been set and exit the program.
Python application (script.py):
import threading
import sys
def quit_watch(event):
input("Type enter to quit")
event.set()
def main():
stop = threading.Event()
threading.Thread(target=quit_watch, args=[stop]).start()
while True:
# do work, regularly check if stop is set
if stop.wait(1):
print("Stopping application loop")
break
if __name__ == '__main__':
main()
sys.exit(0)
Node.js application:
child_process = require('child_process')
child = child_process.spawn('python.exe', ['script.py'])
// check if process is still running
assert(child.kill(0) == true)
// to terminate gracefully, write newline to stdin
child.stdin.write('\n')
// check if process terminated itself
assert(child.kill(0) == false)

Ensure child exits when parent does

I am spawning a child process in node and have sometimes noticed that the child process does not exit when the parent process exits.
var worker = child.fork(__dirname + '/process');
worker.on('exit', function(exitCode) {
if (exitCode != 0) {
start();
}
});
process.on('exit', function() {
worker.kill();
});
I am also trying to ensure that if the worker dies without a 0 error code it is started again. This seems to work just fine. However I have noticed that sometimes my child process is left hanging around after the parent is gone.
What exactly is 'process.on('exit'...' listening for? I.E. What happens when I press Ctrl-C, will exit pick that up? Do I need to listen for all parent exit signals?
Should I be listening for more on the child process as well? Like 'uncaughtException'?
Basically what is the proper way to launch a child process, ensure it stays up, but exits when the parent does?

Resources