Nodejs child process exit before stdio streams close - node.js

I've just been experimenting with child processes and noticed that the exit event fires before the close event - the following code throws an error because this._instance.stdin no longer exists (this._instance is already null).
'use strict';
const spawn = require('child_process').spawn;
class Foo {
constructor() {
this._instance = null;
}
bar() {
this._instance = spawn('ls', ['-l']);
this._instance.on('close', (code, signal) => {
this._instance.stdin.end();
});
this._instance.on('exit', (code, signal) => {
this._instance = null;
});
return this._instance;
}
}
var foo = new Foo();
console.log(foo.bar());
The documentation states:
"Note that when the 'exit' event is triggered, child process stdio streams might still be open."
I wondered how this happens, why do the streams still exists after the process has exited? And how do they get 'closed', is this part handled by the OS or does node do the closing of the left over stdio streams?
In practice I wouldn't necessarily set this._instance to null on exit as it doesn't seem like a nice thing to do and is obviously a bit premature.

The documentation of the close event sheds some light on why this might happen.
In short, the stdio can be used by other processes which did not exit yet.
I haven't looked into the code itself, but it would make sense that the OS handles the part of closing the stdio streams. Think about piping stdio into multiple processes (piping with tee might be a good example).
In the presented case, I would suspect that you don't even need to end() stdin, as the close event suggests that the stdin stream has already been closed.

Related

Wait for process.kill to terminate the process

My application terminates a process (exe file) then attempts to replace it with an updated version, I'm using process.kill with the pid of the process, I keep getting an error when trying to replace it with a newer version because the exe file is still in use and cannot be deleted, I have "resolved" this by waiting for 500ms but I wouldn't call that a good solution, I was expecting the method to be synchronous or at least have a sync counterpart just like the rest of fs methods.
Are there any other ways to do it in node.js?
The docs says the following :
Even though 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.
To me, it implies that process.kill will not indicate that the process is killed, but just that the signal has been sent (when it returns).
But there's another interesting line :
This method will throw an error if the target pid does not exist. As a special case, a signal of 0 can be used to test for the existence of a process. Windows platforms will throw an error if the pid is used to kill a process group.
So you can come up with something like that :
const killProcess = ({pid, signal = 'SIGTERM', timeout} = {}) => new Promise((resolve, reject) => {
process.kill(pid, signal);
let count = 0;
setInterval(() => {
try {
process.kill(pid, 0);
} catch (e) {
// the process does not exists anymore
resolve();
}
if ((count += 100) > timeout) {
reject(new Error("Timeout process kill"))
}
}, 100)
})

Catch SIGINT in parent but kills child anyway

I have a architecture with one parent that spawns tow childs (one in c++ the other in python). The parent spawns with the following class :
export class subProcess {
protected cmd: string;
protected args: string[];
protected process: child.ChildProcess;
constructor(cmd: string, args: string[]) {
this.cmd = cmd;
this.args = args;
this.process = null;
}
spawn(): void {
this.process = child.spawn(this.cmd, this.args);
const rlout = readline.createInterface({
input: this.process.stdout,
});
rlout.on('line', line => this.logger.info(line));
const rlerr = readline.createInterface({
input: this.process.stderr,
});
rlerr.on('line', line => this.logger.error(line));
this.process.on('exit', (code: number) => {
this.logger.info(`exit code: ${code}`);
});
}
When I interrupt the parent whith a Ctrl-C, signal SIGINT is caugth in the parent process to be able to first disconnect and kill the childs gracefully :
process.on('SIGINT', () => {
this.bus.disconnect();
});
disconnect is a function that sends an "exit_process" command to the childs via ZeroMQ. This command works perfectly fine in normal behavior. But the problem is that when I press Ctrl-C, the SIGINT is caugth by the Parent and it executes disconnect function (as expected) but it seems that it also propagate SIGINT to the childs. Indeed, the "exit_process" command sent via ZeroMQ reaches it's timeout (which means that the childs have never received/answered) whereas the childs emit a returned code via the exit event.
The point is that I can't detache and/or unref the childs, or manage signals in childs, for project reasons. And I expected the parent to catch the SIGINT whithout propagating it to the childs.
One more point, I tried to add the following in subProcess class, but it did not work :
this.process.on('SIGINT', () => {
console.log('SIGINT received. Do nothing');
});
Your SIGINT is being passed to the entire process group — see this section on Wikipedia. However, you're probably not seeing any output because of how the child process pipes are established.
When spawning a new child process, you can provide stdio options:
this.process = child.spawn(this.cmd, this.args, {stdio: 'inherit'});
The above causes the parent's process.stdin, process.stdout, process.stderr to be inherited by the child process. If you use this approach, you will see that your child is receiving the SIGINT.
The default behaviour is to create separate streams, which is why you are not seeing your console.log. You could also listen to the child's stdout stream:
this.process.stdout.on('data', data => console.log(data.toString()));

Electron: Perform sqlite (better-sqlite) db operations in another thread

I'm developing a desktop application using Electron framework and I've to use sqlite database for app data.
I decided to use better-sqlite3 because of:
Custom SQL function support (It's very important for me)
It's much faster than node-sqlite3 in most cases
It is simple to use.
It's synchronous API (in most cases I need to get data serialized)
but in some cases, when I perform a query that takes a while to response, the application UI won't responses to user until the query ends.
how can I run some db queries in another thread? or run them asyncronized (like node-sqlite3)?
sorry for bad english
Node allows you a separate process out-of-the-box. ( Threads are a different matter - alas no WebWorkers :( though you can prob find a thread add-on lib somwhere.
EDIT: Node has added worker_threads since I originally posted this answer. Haven't tried it yet / dunno if they work with better-sqlite.END EDIT
I've had the same issue as you - needing synchronous code to run without blocking the main thread and I used a child process. It was for better-sqlite too !
Problem is that how to handle io streams and sigints etc for control is not immediately obvious and differs depending on whether you're running on windows or posix.
I use a forked child process with silent option set to true to do the synchronous db work.
If you need control of that process or progress update reports back to your main process for your gui during sync ops ; I control/communicate with the child process by reading/writing on the child process stdin/out using fileSystem writeFileSync / readFileSync at various points in my child process code ( you can't use the normal inter-process comms api during sync ops as that's event driven and can't operate while synchronous code is running. Though you can mix and match the two types of io)
example of forked child process ;
//parent.js and child.js in same folder
//parent.js
process.on('exit', (code) => {
console.log(`Parent to exit with code: ${code}`);
});
const readLine = require("readline") ;
const cp = require('child_process');
var forkOptions = {
//execArgv:['--inspect-brk'], // uncomment if debugging the child process
silent:true // child gets own std pipes (important) whch are piped to parent
};
var childOptions = [] ;
const child = cp.fork(`./child.js`,childOptions,forkOptions);
//for messages sent from child via writeSync
const childChannel = readLine.createInterface({
input: child.stdout
}).on("line",function(input){
console.log("writeSync message received from child: " + input) ;
});
//for messages sent from child via process.send
child.on('message', (m) => {
console.log("process.send message received from child: " + m) ;
});
// Child.js
process.on('exit', (code) => {
console.log(`Child to exit with code: ${code}`);
});
const fs = require('fs');
function doSyncStuff(){
for(let i = 0 ; i < 20 ; i++){
//eg. sync db calls happening here
process.send(`Hello via process.send from child. i = ${i} \n`); // async commms . picked up by parent's "child.on" event
fs.writeFileSync(process.stdout.fd,`Hello via writeFileSync from child. i = ${i} \n`) ; // sync comms. picked up by parent's readLine listener ("process" here is the child )
}
}
doSyncStuff();

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.

Node.JS Parent Process ID

Is it possible to get the parent process-id using Node.JS? I would like to detect if the parent is killed or fails in such a way that it cannot notify the child. If this happens, the parent process id of the child should become 1.
This would be preferable to requiring the parent to periodically send a keep-alive signal and also preferable to running the ps command.
You can use pid-file. Something like that
var util = require('util'),
fs = require('fs'),
pidfile = '/var/run/nodemaster.pid';
try {
var pid = fs.readFileSync(pidfile);
//REPLACE with your signal or use another method to check process existence :)
process.kill(pid, 'SIGUSR2');
util.puts('Master already running');
process.exit(1);
} catch (e) {
fs.writeFileSync(pidfile, process.pid.toString(), 'ascii');
}
//run your childs here
Also you can send pid as argument in spawn() call
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