Node: Read spawn stderr/stdout after quick exit event? - node.js

I'm using Node's spawn to create a new process. Sometimes this process will exit very quickly with an error code and a message on stderr. It appears that stderr is getting lost in this quick turnaround. I've tried this:
reader.stderr.on('data', function (buf) {
console.log('stderr message: ' + buf);
});
reader.on('exit', function (code, signal) {
console.log('Exit');
});
Output:
Exit
stderr message: ERROR: Missing required option for command.
I also tried reading it in the exit listener, but no luck:
reader.on('exit', function (code, signal) {
console.log('Exit');
console.log('stderr: ' + reader.stderr.read());
});
Output:
Exit
stderr: null
So, it appears the problem is that the stderr output is too slow, and is late after the exit event where I need that information. How can I fix this?

Taken from the child_process docs for exit:
Note that the child process stdio streams might still be open.
They then describe the close event:
This event is emitted when the stdio streams of a child process have all terminated. This is distinct from 'exit', since multiple processes might share the same stdio streams.
So it looks like you should be using close, not exit.

Related

Executing shell command using child process

I am trying to execute a command to fork a chain using ganache. I have struggled to find a way to do the fork programmatically so I want to try execute it through a child process.
The code I am using is:
const { exec } = require("child_process");
const ganache = require("ganache");
const ls = exec('ganache-cli --fork https://speedy-nodes-nyc.moralis.io/KEY_HERE/bsc/mainnet/archive', function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: ' + error.code);
console.log('Signal received: ' + error.signal);
}
console.log('Child Process STDOUT: ' + stdout);
console.log('Child Process STDERR: ' + stderr);
});
ls.on('exit', function (code) {
console.log('Child process exited with exit code ' + code);
});
But when I run this, No output is given at all. No errors or anything it just carries on with the program asif this code did not exist. When I replace the command with 'dir', it works perfectly and lists the files in the directory. Even if I remove the node address and just use "ganache-cli --fork" as the command, nothing happens.
Why does nothing happen when I add the ganache commands?
If you take a look at the child process docs, they say exec:
spawns a shell and runs a command within that shell, passing the stdout and stderr to a callback function when complete.
However, Ganache is a process that continues running and doesn't "complete" until you kill it. This allows you to send multiple requests to Ganache without it shutting down on you.

Unknown signal error as I try to kill a process

I am trying to kill a process using the kill method in child_process. But I get the following error as I try to call the function:
TypeError [ERR_UNKNOWN_SIGNAL]: Unknown signal: 18408
I am doing as follows:
const ls = spawn('node',['print']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
setTimeout(() => {
ls.kill(ls.pid);
},4000);
What could be the reason for this? What am I missing?
kill() sends a Unix signal. These are represented by numbers. You should pass the number of a signal to kill(), not the PID.
Try
import signal
print(signal.SIGTERM)
That probably prints 15 which is the number of the signal TERM (which normally terminates the process it is sent to).
In your program you could call
ls.kill(signal.SIGTERM)
In fact, unlike the Linux kill() command, which takes the process pid to kill and a signal as parameters, Nodejs ChildProcess subprocess.kill() expects only the signal argument (as string according to the documentation).
subprocess.kill([signal])
That makes sense because the subprocess already knows its pid (the one you get when using subprocess.pid) so you don't need to provide it again.
To kill your process, you can pass a valid signal to the .kill() method:
ls.kill('SIGTERM');
Or, you could also simply call the .kill() method of your subprocess without any arguments, which is equivalent to call it with the 'SIGTERM' signal.
ls.kill()

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.

Difference between ChildProcess close, exit events

When spawning child processes via spawn()/exec()/... in Node.js, there is a 'close' and an 'exit' event on child processes.
What is the difference between those two and when do you need to use what?
Before Node.js 0.7.7, there was only an "exit" event on child processes (and no "close" event). This event would be fired when the child process has exited, and all streams (stdin, stdout, stdout) were closed.
In Node 0.7.7, the "close" event was introduced (see commit).
The documentation (permalink) currently says:
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.
If you just spawn a program and don't do anything special with stdio, the "close" event fires after "exit".
The "close" event can be delayed if e.g. the stdout stream is piped to another stream. So that means that the "close" event can be delayed (indefinitely) after the "exit" event.
Does this mean that the "close" event is always fired after "exit"? As the examples below show, the answer is no.
So, if you are only interested in the process termination (e.g. because the process holds an exclusive resource), listening for "exit" is sufficient.
If you don't care about the program, and only about its input and/or output, use the "close" event.
Experiment: destroy stdio before killing child
Experimentally (in Node.js v7.2.0), I found that if the stdio streams are not used by the child process, that then the "close" event is only fired after the program has exited:
// The "sleep" command takes no input and gives no output.
cp = require('child_process').spawn('sleep', ['100']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cp.stdin.end();
cp.stdout.destroy();
cp.stderr.destroy();
console.log('Closed all stdio');
setTimeout(function() {
console.log('Going to kill');
cp.kill();
}, 500);
The above program spawning "sleep" outputs:
Closed all stdio
Going to kill
exited null SIGTERM
closed null SIGTERM
When I change the first lines to a program that only outputs,
// The "yes" command continuously outputs lines with "y"
cp = require('child_process').spawn('yes');
... then the output is:
Closed all stdio
exited 1 null
closed 1 null
Going to kill
Similarly when I change spawn a program that only reads from stdin,
// Keeps reading from stdin.
cp = require('child_process').spawn('node', ['-e', 'process.stdin.resume()']);
Or when I read from stdin and output to stdout,
// "cat" without arguments reads from stdin, and outputs to stdout
cp = require('child_process').spawn('cat');
Experiment: Pipe program to another, kill first program
The previous experiment is quite artificial. The next experiment is a bit more realistic: You pipe a program to another and kill the first one.
// Reads from stdin, output the input to stdout, repeat.
cp = require('child_process').spawn('bash', ['-c', 'while read x ; do echo "$x" ; done']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);
setTimeout(function() {
// Let's assume that it has started. Now kill it.
cp.kill();
console.log('Called kill()');
}, 500);
Output:
Called kill()
exited null SIGTERM
closed null SIGTERM
Similarly when the first program only reads from input and never outputs:
// Keeps reading from stdin, never outputs.
cp = require('child_process').spawn('bash', ['-c', 'while read ; do : ; done']);
When the first program keeps outputting without waiting for stdin, the behavior is different though, as the next experiment shows.
Experiment: Pipe program with lots of output to another, kill first program
// Equivalent to "yes | cat".
cp = require('child_process').spawn('yes');
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);
setTimeout(function() {
// Let's assume that it has started. Now kill it.
cp.kill();
console.log('Called kill()');
setTimeout(function() {
console.log('Expecting "exit" to have fired, and not "close"');
// cpNext.kill();
// ^ Triggers 'error' event, errno ECONNRESET.
// ^ and does not fire the 'close' event!
// cp.stdout.unpipe(cpNext.stdin);
// ^ Does not appear to have any effect.
// ^ calling cpNext.kill() throws ECONNRESET.
// ^ and does not fire the 'close' event!
cp.stdout.destroy(); // <-- triggers 'close'
cpNext.stdin.destroy();
// ^ Without this, cpNext.kill() throws ECONNRESET.
cpNext.kill();
}, 500);
}, 500);
The above program outputs the following and then exits:
Called kill()
exited null SIGTERM
Expecting "exit" to have fired, and not "close"
closed null SIGTERM
the short version is, 'exit' emits when the child exits but the stdio are not yet closed.
'close' emits when the child has exited and its stdios are closed.
Besides that they share the same signature.

Node.js intercepting process.exit

So I wanted to run a shell command at the end of my node.js program and wait for the output/print it before exiting. I tried process.on('exit',function(){}) and running the child exec command in there but the program exited before the callback. So instead I used a closure on process.exit but I am getting some strange results. The basics of the code is:
process.exit = (function(old_exit){
return function(code){
var exec = require('child_process').exec;
var child;
child = exec("my shell command", function (error, stdout, stderr) {
if (error !== null) {
console.log('exec error: ' + error);
}
console.log(stdout);
//I first had this as the concluding line:
old_exit.apply(process,arguments);
//and I also tried
old_exit.apply(process,[]);
//and even (which I know is not in the right scope)
old_exit(code);
//and also
process.exit = old_exit;
process.exit(code);
});
}
}(process.exit));
Every one of the above results executed my shell command exactly twice and then exited. I also tried not calling anything at the end and while that kept it so that my command executed only once, the process hung instead of exiting at the end. Unless there is something simply I'm missing I feel like the first attempt I had old_exit.apply(process,arguments); would be the correct way and should not be calling my own code again, it does. I also tried used promises which didn't work (it didn't even throw an error for being resolved multiple times) and I tried using a boolean for if it had been set but that didn't work either. I finally even tried throwing an error after the callback finished but this forced process.exit to be called again after the error. Any ideas?
By process.exit time, you are too late to do anything async. I do not think you can get around the async part, as you apparently need to execute a shell command. You can listen for various exit signals, do your async stuff, then call process.exit yourself when you are ready. Note you will get a all signals until the process exists, so you will want to track the state to ensure your shell command is only run once. The following will work for the ctrl-c signal:
process.on('SIGINT', function() {
console.log('Received Signal');
setTimeout(function() {
console.log('Exit');
process.exit(1);
}, 10000);
});

Resources