How to tell if a window failed to close in Electron? - node.js

Is there a way in Electron to tell if a window was not successfully closed?
win.once("closed", () => {
// send message to the page running in the renderer process that the window was closed
});
win.close();
Assuming that I'm not cancelling the close in the close or beforeunload handler, can the window still fail to close, or can I be sure that a message will always be sent to the guest page?

I just came to this issue as well, what I did is simply wait for a few hundred milliseconds, if the callback is called, then most likely, this window has failed to close:
win.on('close', () => setTimeout(() => console.log('failed to close'), 300))

Have a look at this property in the doc:
win.closed
A Boolean that is set to true after the child window gets closed.
And this other bit too:
win.destroy()
Force closing the window, the unload and beforeunload
event won’t be emitted for the web page, and close event will also not
be emitted for this window, but it guarantees the closed event will be
emitted.
With that, you should have all the info you need to create a function that insure you that the window closes:
function forceClose(window) {
// try close first
window.close()
// force with destroy
if(!window.closed) {
window.destroy()
}
//just logging out the event
window.on('closed', (e) => {
console.log(e)
})
}
// in your code, instead of calling win.close()
forceClose(win)

Related

Node/Typescript: How to close a writestream in the process 'exit' event - red/blue function problem / async infection

In my current FOSS Discord bot project I have this log.ts file which handles the logging for the bot.
It creates multiple fs.WriteStream objects, which write to each log file. There is a section in the code when await log('CLOSE_STREAMS') is called to run the WriteStream#close() functions on each WriteStream, returning a promise. This is used in the process.on('exit') handler to save the log files before we close.
The problem here is that the 'exit' event handler can not schedule any additional work into the event queue.
How could I handle calling the CLOSE_STREAMS in a way where I can run this exit handler as I am expecting?
Function Implementation, simplified
log.ts log('CLOSE_STREAMS')
// Main
export default function log(mode: 'CLOSE_STREAMS'): Promise<void>;
export default function log(mode: 'v' | 'i' | 'w' | 'e', message: any, _bypassStackPrint?: boolean): void;
// eslint-disable-next-line #typescript-eslint/explicit-module-boundary-types
export default function log(mode: 'v' | 'i' | 'w' | 'e' | 'CLOSE_STREAMS', message?: any, _bypassStackPrint = false): void | Promise<void> {
if (mode === 'CLOSE_STREAMS')
// Close all of the file streams
return new Promise((resolve) => {
errStr.end(() => {
warnStr.end(() => {
allStr.end(() => {
resolve();
});
});
});
});
else {
index.ts
This is the way the log is killed in the uncaught exceptions; this would be how I want to do it for the exit event.
// If we get an uncaught exception, close ASAP.
process.on('uncaughtException', async (error) => {
log('e', 'Killing client...', true);
client.destroy();
log('e', 'Client killed.', true);
log('e', 'Closing databases...', true);
client.closeDatabases();
log('e', 'Closed databases.', true);
log('e', 'An uncaught exception occured!', true);
log('e', `Error thrown was:`, true);
error.stack?.split('\n').forEach((item) => {
log('e', `${item}`, true);
});
log('e', 'Stack trace dump:', true);
let stack = new Error().stack?.split('\n');
stack?.shift();
if (!stack) stack = [];
stack.forEach((item) => {
log('e', `${item}`, true);
});
log('e', 'Process exiting.', true);
log('e', 'Exit code 5.', true);
log('e', 'Goodbye!', true);
await log('CLOSE_STREAMS'); // <<<<<<<<<<<< HERE
process.exit(5);
});
As you know, you can't reliably use asynchronous operations in the processing of an exit event because the process will exit before they complete. As such, I don't think there's any way to do this reliably with a nodejs stream. Streams have a completely asynchronous API and implementation, including flushing and closing.
In a few Google searches I found a few other people asking for the same thing for the same reasons and there was no solution offered. As best I know, these are the options:
Do some hacking on the stream object to add a synchronous flushAndClose() method to your stream. You'd have to work into the internals of the stream to get the buffer and the file handle and do your own synchronous write of any remaining buffer and then do a synchronous close on the file handle. Note, even this has a problem case if there's currently an asynchronous write operation in process.
Abandon the built-in stream and just implement your own lightweight logfile interface that makes it easy for you to have both asynchronous writing (for normal use) and a synchronous flushAndClose() operation for emergency shut-down. Note, even this has a problem case if there's currently an asynchronous write operation in process when you want to do the synchronous close.
Rather than using process.on('exit', ...) to trigger closing of the log files, go up one level higher in the chain. Whatever it is that triggers closing of your app, put it in an async function that will wait for the log files to be properly closed before calling process.exit() so you get the log files closed when you still have the ability to handle asynchronous operations.
Do logging from a different (more stable) process. This process can then message that logging process what it wants logged and that process can manage getting the logging info safely to disk independent of whether the source process shuts down abruptly or not.
Note: Exiting a process will automatically close any open file selectors (the OS takes care of that for you). So, as long as this is an edge case shut-down in some fatal error condition, not a common normal shut-down, then perhaps you don't have a big problem here to really solve.
The worst that could happen is that you might lose some very recently logged lines of data if they hadn't yet been flushed from the stream. Note that streams write data immediately to their descriptor when possible so they don't generally accumulate lots of buffered data. The time when they do buffer data is when a new write to the stream happens, but the previous write is still in operation. Then the data to be written gets buffered until the prior write operation completes. So, data is never left sitting in the buffer with an idle stream. This tends to minimize (though not eliminate) data loss on an immediate shut-down.
If this is a normal, regular shut-down, then you should be able to use option #3 above and reshape how the shut-down occurs so you can use asynchronous code where you want so you can properly shutdown the streams.

How to log stack trace on node.js process error event

My node process is dying and I can't seem to log to a file when the process exits. It is a long running process invoked directly with node index.js:
// index.js
const fs = require('fs');
exports.getAllCars = (process => {
if (require.main === module) {
console.log(`Running process: ${process.getgid()}.`);
let out = fs.createWriteStream(`${__dirname}/process.log`);
// trying to handle process events here:
process.on('exit', code => out.write(`Exit: ${code}`));
return require('./lib/cars').getAllCars();
} else {
return require('./lib/cars').getAllCars;
}
})(process);
Also tried creating event handlers for error, uncaughtException. Nothing works when killing my process manually (with kill {pid}). The file process.log is created but nothing is there. Do writeable streams require a stream.end() to be called on completion?
According to Node.js documentation:
The 'exit' event is emitted when the Node.js process is about to exit
as a result of either:
The process.exit() method being called explicitly.
The Node.js event loop no longer having any additional work to perform.
So, if you start a process that should never end, it will never trigger.
Also, writable streams do not require to be closed:
If autoClose(an option from createWriteStream) is set to true (default
behavior) on error or end the file descriptor will be closed
automatically.
however, the createWriteStream function opens the file with flag 'w' by default, which means that the file will be overwritten every time (maybe this is the reason why you always see it empty). I suggest to use
fs.appendFileSync(file, data)
Here are the events that want to listen:
//catches ctrl+c event
//NOTE:
//If SIGINT has a listener installed, its default behavior will be removed (Node.js will no longer exit).
process.on('SIGINT', () => {
fs.appendFileSync(`${__dirname}/process.log`, `Received SIGINT\n`);
process.exit()
});
//emitted when an uncaught JavaScript exception bubbles
process.on('uncaughtException', (err) => {
fs.appendFileSync(`${__dirname}/process.log`, `Caught exception: ${err}\n`);
});
//emitted whenever a Promise is rejected and no error handler is attached to it
process.on('unhandledRejection', (reason, p) => {
fs.appendFileSync(`${__dirname}/process.log`, `Unhandled Rejection at: ${p}, reason: ${reason}\n`);
});
I suggest you put the code in a try catch block to find out whether its the code or some external cause which results in program termination.
and then check the log after the event...
try {
//your code
}catch(e) {
console.log(e.stack);
}

Why does the data event only stop the NodeJS run time from exiting?

Take the following code in nodejs-:
console.log("Hello world");
process.stdin.on('connect', function() {
});
This prints Hello World and then Node exits. But when I replace the connect event with 'data' event, the Node runtime does not exit.
Why is that ? What is so special about the EventEmitter's data event ? Does it open a socket connection ? So in the on() method is there code like the following -:
function on(event, callback) {
if(event === 'data') {
//open socket
//do work
}
else {
//do non-socket work
}
}
Is there a clear answer to why adding a listener to the data event "magically" open a socket.
Node.js event loop has couple phases of processing, in your case it's poll phase. Which process for example incoming data (process.stdin.on('data', cb)) so until there is a callback that can handle this event, a this event can occur, node event loop is not empty and node will not exit.
process.stdin is Readable Stream which has fallowing events:
close
data
end
error
readable
so there is nothing like connect.
process.stdin.on('readable', function() {
console.log('Readable');
});
Code above will print Readable and exit because after firing event stream is not in flowing state so it will exit event loop because it's empty, but data event sets stream state to flowing,
so if stream state is set to flowing and if readableState.reading is true it will prevent node to exit.
if you do
process.stdin.on('data', function(data) {
console.log(data.toString());
});
if you write anything in console when this is running it will work like echo.
https://github.com/nodejs/node/blob/master/lib/_stream_readable.js#L774-L795
You can read full explanation how event loop works here https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
If we assume index.js contains
process.stdin.on('data', console.log)
and you do node index.js, then the process waits for input on stdin.
If you send some data on stdin via e.g. echo woohoo | node index.js then the buffer will be written and the process exits.

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.

Catching jPlayer full screen event

I'm trying to catch jPlayer full screen event using the following code:
mainPlayer.bind($.jPlayer.event.resize, function(event) {
alert("size changed");
});
But the event isn't fired neither when I press full screen button nor when I press ESC to exit full screen mode.
Check your mainPlayer variable and make sure it references the jQuery jPlayer object. For troubleshooting, try:
$('#yourJPlayerID').bind($.jPlayer.event.resize, function(event) {
alert("size changed");
});

Resources