Node.js intercepting process.exit - node.js

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

Related

Can I “listen” for a specific output with child_process?

So far I have gotten my script to execute a windows .bat file with child_process, my issue is that it opens it in the background with no way to “connect” to it to see what happens and debug, is there a way to “listen” for a certain output to happen? For example, if the .bat outputs a “Done!” in the shell at one point, is there a way to make my node.js script detect that certain keyword and run further commands if it does?
Thanks!
Some clarification: The .bat outputs "Done!" and stays running, it doesn't stop, all I want to do is detect that "Done!" so that I can send a message to the user that the server has successfully started
My current code:
exec('D:\\servers\\game_server_1\\start.bat', {shell: true, cwd: 'D:\\servers\\game_server_1'});
Well, if you're trying to do a one and done type of NodeJS script, you can just spawn a process that launches with the given command and exits when all commands completed. This creates a one and done streaming interface that you can monitor. The stdout returns a data buffer that returns the command you ran, unless it's something like START to launch a program-- it returns null. You could just issue a KILL command after the START -- your_program.exe:
const spawn = require('child_process').spawn;
const child = spawn('cmd.exe', ['/c', 'commands.bat']);
let DONE = 0;
const done = () => {
console.log("log it");
DONE++;
};
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
//it's important to add some type of counter to
//prevent any logic from running twice, since
//this will run twice for any given command
if ( data.toString().includes("DONE") && DONE === 0 ) {
done();
}
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('exit', function (code) {
console.log('child process exited with code ' + code);
});
Keep in mind, when you run a command to launch a program and the program launches, the data buffer will be null in stdout event listener. The error event will only fire if there was an issue with launching the program.
YOUR .BAT:
ECHO starting batch script
//example launching of program
START "" https://localhost:3000
//issue a command after your program launch
ECHO DONE
EXIT
You could also issue an ECHO DONE command right after the command where you launched the program and listen for that, and try and parse out that command from stdout.
You could use a Regular expression.
const { spawn } = require('child_process');
const child = spawn(...);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
// Now use a regular expression to detect a done event
// For example
data.toString().match(/Done!/);
});
// Error handling etc. here

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.

Meteor methods, trying to call child_process.spawn, getting TypeError: child_process.spawn is not a function

I am trying to call Meteor.method from inside the client component:
Meteor.call('execute', this.parameter);
Meteor.methods have a function which spawns the process as follows:
cp.spawn(pathtoscript, ['-t', parameter.myid], options);
This is a valid process spawn which is executed successfully (it takes up to 30 seconds to complete), however browser console spits out an error immediately after call is made:
Exception while simulating the effect of invoking 'execute' TypeError:
cp.spawn is not a function(…) TypeError: cp.spawn is not a function
I have tried just spawning the process and exiting the function and I have also tried to wait for 'close' event. Both times execution on the backend is successful, but browser console throws exception.
I have also tried to call Meteor.methods asynchronously
Meteor.call('execute', this.parameter, function(error, result) {
if (error) {
alert(error, error.reason);
}
console.log(result);
});*/
While adding return values in Meteor.methods. And it always ends in the same way.
Can you please advise the proper way for spawning processes in such cases?
This is because your method code is in both client and server. It can not run on client because there is no spawn in browser.
To fix this you could simply move your method to server code only or just wrap it inside an if statement with condition Meteor.isServer:
if (Meteor.isServer) {
Meteor.methods({
execute(params) {
//...
}
});
}

How to keep a node.js script alive while promises are being resolved?

My script performs some asynchronous tasks using promises (with the q library). Running mocha tests works fine. However running the script from the command line does not. The node process immediately dies.
var bot = require('./bot');
bot.getCategories().then(function (categories) {
console.log('Found ' + categories.length + ' categories');
});
My script performs some asynchronous tasks using promises (with the q library). Running mocha tests works fine. However running the script from the command line does not. The node process immediately dies.
This is most certainly a bug, please do report it. The Node.js environment should not exit prematurely while there are things still queued in the event loop.
You should not have to alter your code one bit for this to happen. The Q library (keep in mind there are more modern and native alternatives today) schedules async callbacks on the process.nextTick "microtask" queue. Your bot library presumably also performs IO, both these things should cause node not to terminate.
Node.js will exit when there are no more callbacks to process. You can use setInterval or setTimeout to always keep one so that the process does not automatically exit.
function wait () {
if (!EXITCONDITION)
setTimeout(wait, 1000);
};
wait();
Let's start like this:
'use strict'
const timeoutPromise = (time) => {
return new Promise((resolve, reject) => { setTimeout(() =>
{ console.log('howdy'); resolve('done') }, time) })
}
below we do...
Ending A) - simple promise
console.log('start')
timeoutPromise(1000)
console.log('end')
start and end will appear immediately.
it will take another second to see 'howdy' and get our terminal prompt back. (so in that sense the main script is kept alive, but presumably not what the OP wants...)
Ending B) - waiting for promise return
console.log('start')
return timeoutPromise(1000)
console.log('end')
start will appear, after 1 second 'howdy' will appear. 'end' is unreachable. So here we truly wait for the promises and could do things with them…
Ending C) - then()
console.log('start')
return timeoutPromise(1000).then((result) => {
console.log('end', result)
process.exit(123) // usually 0 for 'ok', just demo!
}
)
start will appear, one seconds goes by and 'howdy', 'end' and 'done' appears. And could be used to send a return value.
$>node haveMainWait.js
start
howdy
end done
$>echo $? // return value of last command
123
Almost certainly you want a .catch() after the .then() in case the promise gets rejected... (and returning a nonzero exit code only in that case)
Instead of a single Promise like timeoutPromises(…) you could of course use Promise.all(…) or an async/await-Function (which has to be wrapped back to promises somewhere on the hierarchical way up... you have that covered here, too).

Performing Operations while Closing NodeJS

I have a Firebase Connection in nodejs that pushes data to a url while the connection is persistent, when it closes, I want to remove that data (think, I push "hey I'm here", and when I leave, the text disappears)
I made a "runnable" that shows an example of it:
http://web-f6176e84-c073-416f-93af-62a9a9fbfabd.runnable.com
basically, hit "ctrl + c" and it prints out "trying to remove reference" but never actually deletes the data ( the documents say that remove() is equivalent to set(null) which it basically sets the data to null, and since it's null, the entire element should be gone.)
However it's not removing it, I don't see the data ever "disappear". (I'm using a temp Firebase URL, you should be able to duplicate with any URL you can access if this url stops existing).
this is the code I'm using.
var FB_URL = 'https://cuhiqgro1t3.firebaseio-demo.com/test_code';
var Firebase = require('firebase');
var myRootRef = new Firebase(FB_URL);
console.log("created Firebase URL");
process.stdin.resume(); //so the program will not close instantly
function delete_fb_entries() {
return function() {
console.log("Trying to remove reference");
myRootRef.remove();
process.exit();
}
}
//do something when app is closing
process.on('exit', delete_fb_entries());
//catches ctrl+c event
process.on('SIGINT', delete_fb_entries());
//catches uncaught exceptions
process.on('uncaughtException', delete_fb_entries());
EDIT: Additional Information as to the "why", I push my local IP address out to my Firebase URL cause I'm lazy and it's easier to just have a webpage setup I can always access that will show the url of particular devices (and I know using the routers tables would be easier), I actually also have other purposes for this usage as well (if I happen to be inside my network, I can just select a particular device from my webpage and access the data I need, either way, it works, but I just can't get it to remove itself correctly, this used to work at one point in time I believe, so I can only assume the API has changed or something).
EDIT 2: OK removed process.exit() as suggested, and the runnable seemed to delete the data in question, I tried it on my local data (and after some cleaning up and commenting out), it removed the data, however when I hit Ctrl + C it no longer exits the program.....so yay.
I need to figure out if "process.exit()" is necessary or unnecessary at this point.
Edit 3: Ok so I need to use process.exit (as far as I can tell, Ctrl + C no longer exits the program, I have to ctrl + Z, and reboot). I tried adding it right after, but I realized that removing a firebase element is not a synchronus operation, so when I close it I tried (the next attempt) was to use the on complete handler for the remove function (so remove(onComplete), and then adding the process.exit() to the onComplete function).
So finally it looks like this and it seems to be working with my application
var FB_URL = 'https://cuhiqgro1t3.firebaseio-demo.com/test_code';
var Firebase = require('firebase');
var myRootRef = new Firebase(FB_URL);
console.log("created Firebase URL");
function onComplete() {
process.exit();
]
process.stdin.resume(); //so the program will not close instantly
function delete_fb_entries() {
return function() {
console.log("Trying to remove reference");
myRootRef.remove(onComplete);
}
}
//do something when app is closing
process.on('exit', delete_fb_entries());
//catches ctrl+c event
process.on('SIGINT', delete_fb_entries());
//catches uncaught exceptions
process.on('uncaughtException', delete_fb_entries());
EDIT 4: In response to comments below, So I tried modifying a simple program to be the following:
function delete_fb_entries (){
return function () {
console.log("I should quit soon");
}
}
process.stdin.resume(); //so the program will not close instantly
//catches ctrl+c event
process.on('SIGINT', delete_fb_entries());
My program never exited. I don't understand why node would not close in this case, changing to add a process.exit() after the console.log causes nodejs to quit. This is not an async function, so why is it not exiting in this case? (Is this a bug, or a misunderstanding of how this works by me?)
You cannot perform asynchronous operations in a process's exit event handler, only synchronous operations, since the process is exited once all exit event handlers have been executed.

Resources