Nodejs: Send Ctrl+C to a child process on Windows - node.js

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)

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));
}

end child gracefully when doing Ctrl-C in NodeJS

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

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 launch Chrome from node.js as a separate process and return immediately?

The objective is to launch Chrome or Chromium from nodejs using child_process, and return immediately, similar to how the windows START command launches a completely separate process and the calling process can exit immediately.
The { shell: true } option for child_process.execFile() almost does the job, in that it separates the node process from the Chrome process; I can exit the main nodejs process with Ctrl+C, and the launched browser remains open. Without that option, they remain married and ^C in node closes Chrome.exe.
What I need, however, is for node to exit completely after launching Chrome. There is apparently no adverse effects of pressing ^C. So if ^C is possible to exit node, why won't it exit immediately? I suspect until the chrome process object is destroyed, node can't exit in good conscience.
What is interesting: If the same Chrome.exe happens to be running already, the "new" Chrome I am launching starts a new tab or Window in that existing chrome and exits. In that case the nodejs script exits immediately.
const child_process = require('child_process');
let ex = "C:\\PROGRA~2\\Google\\Chrome\\APPLIC~1\\chrome.exe";
let chrome = child_process.execFile(ex, [
// tried various Chromium switches here but nothing helped
], {
shell: true, // this spawns a separate process but node won't exit
} , function(err, data) {
console.log(err)
console.log(data.toString());
});
chrome.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
chrome.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
chrome.on('exit', function (code) {
console.log('child process exited with code ' + code);
// chrome.kill();
});
Expected: Since the nodejs can be killed with ^C, why does it even continue running / blocking? I would expect it to exit after it launched Chrome.exe.
However, in actuality, nodejs blocks until I exit Chrome, or press ^C.
I also tried without callback function and .stdout, .stderr and .on hooks -- they don't seem to help or hurt. Node always blocks till I ^C or the child process, albeit separate, exits.
Posting r3wt's comment as answer here: use process.exit(0) to exit your script with non-error. it doesn't exit immediately because there are running EventEmitter(s)
const path = require('path');
const spawn = require('child_process').spawn;
chrome = spawn(
path.join(__dirname, '/path_to_chrome'),
[ ... your chrome switches here],
{
shell: true, // use to run in a shell if you need
detached: true, // important
stdio: 'ignore' // important
}
);
chrome.unref(); // this one is important too
To achieve both: possibility to exit immediately and keep subprocess running, - follow the instructions below.
Use subProcess.unref() which is explained here
It prevents parent from waiting for a given subprocess to exit.
One more thing is detached option together with stdio:'ignored' which allow you to keep your subprocess alive even after you stop nodejs process.

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