summaryrefslogtreecommitdiffhomepage
path: root/app/shutdown-handler.js
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-06-11 18:51:51 +0200
committerAndrej Mihajlov <and@mullvad.net>2018-06-12 16:22:59 +0200
commit190e672d4f483a39e2f12264e823b02f6856a3c2 (patch)
tree9583e5ebbef9f62e6d30352f4fb7b3d693f48466 /app/shutdown-handler.js
parenta7da96bc03b3705c4d99bbc55e9f1a04f2030c25 (diff)
downloadmullvadvpn-190e672d4f483a39e2f12264e823b02f6856a3c2.tar.xz
mullvadvpn-190e672d4f483a39e2f12264e823b02f6856a3c2.zip
Add ShutdownHandler for Main and Renderer
Diffstat (limited to 'app/shutdown-handler.js')
-rw-r--r--app/shutdown-handler.js92
1 files changed, 92 insertions, 0 deletions
diff --git a/app/shutdown-handler.js b/app/shutdown-handler.js
new file mode 100644
index 0000000000..afd10f61b2
--- /dev/null
+++ b/app/shutdown-handler.js
@@ -0,0 +1,92 @@
+// @flow
+import { app, ipcMain, ipcRenderer } from 'electron';
+import { log } from './lib/platform';
+import type { WebContents } from 'electron';
+
+// The timeout before the shutdown is enforced
+const SHUTDOWN_TIMEOUT = 3000;
+
+/**
+ * Shutdown coordinator postpones the application shutdown until ShutdownHandler finished the
+ * shutdown sequence.
+ *
+ * ShutdownCoordinator can only be created from main process.
+ */
+class ShutdownCoordinator {
+ _canQuit = false;
+ _isQuitting = false;
+ _webContents: WebContents;
+ _shutdownTimeout: ?TimeoutID;
+
+ constructor(webContents: WebContents) {
+ this._webContents = webContents;
+
+ app.on('before-quit', this._onBeforeQuit);
+ ipcMain.on('app-shutdown-reply', this._onShutdownReply);
+ }
+
+ dispose() {
+ app
+ .removeListener('before-quit', this._onBeforeQuit)
+ .removeListener('app-shutdown-reply', this._onShutdownReply);
+ }
+
+ _onBeforeQuit = (event) => {
+ if (!this._canQuit) {
+ // make sure we don't call the shutdown handler twice
+ if (!this._isQuitting) {
+ this._isQuitting = true;
+
+ // start timer to force shutdown if the renderer process is not able to handle shutdown in
+ // a timely manner.
+ this._shutdownTimeout = setTimeout(this._onShutdownTimeout, SHUTDOWN_TIMEOUT);
+
+ this._webContents.send('app-shutdown');
+ }
+
+ event.preventDefault();
+ }
+ };
+
+ _onShutdownReply = () => {
+ const shutdownTimeout = this._shutdownTimeout;
+ if (shutdownTimeout) {
+ clearTimeout(shutdownTimeout);
+ }
+ this._grantShutdown();
+ };
+
+ _onShutdownTimeout = () => {
+ log.warn('It took longer than expected to shutdown. Forcing shutdown now.');
+ this._grantShutdown();
+ };
+
+ _grantShutdown() {
+ this._canQuit = true;
+ app.quit();
+ }
+}
+
+/**
+ * Executes the provided closure when application is about to quit.
+ * It works in tandem with ShutdownCoordinator and can only be used in renderer process.
+ * The shutdown will be postponed until the Promise returned from closure is resolved.
+ * ShutdownCoordinator will force the shutdown if the returned Promise is not resolved within
+ * the allocated time (see SHUTDOWN_TIMEOUT).
+ */
+function setShutdownHandler(handler: () => Promise<void>) {
+ ipcRenderer.once('app-shutdown', async (event) => {
+ try {
+ await handler();
+ } catch (error) {
+ log.warn(`An error occurred in shutdown handler: ${error.message}`);
+ }
+ event.sender.send('app-shutdown-reply');
+ });
+}
+
+if (ipcMain) {
+ module.exports = { ShutdownCoordinator };
+} else {
+ module.exports = { setShutdownHandler };
+}