summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-11-14 18:03:52 +0100
committerAndrej Mihajlov <and@mullvad.net>2018-11-15 11:24:19 +0100
commit8cc8423ac343893e7d622efe665b8083d9b9fc0f (patch)
tree6ae7781c6dc1d2fe2d10b21666e8c1f982f026c0
parent7fceec2f4684ef6975576ac85842ef0377ce1a3d (diff)
downloadmullvadvpn-8cc8423ac343893e7d622efe665b8083d9b9fc0f.tar.xz
mullvadvpn-8cc8423ac343893e7d622efe665b8083d9b9fc0f.zip
Move shutdown handling to the main process
-rw-r--r--gui/packages/desktop/src/main/index.js57
-rw-r--r--gui/packages/desktop/src/main/shutdown-coordinator.js69
-rw-r--r--gui/packages/desktop/src/renderer/app.js11
-rw-r--r--gui/packages/desktop/src/renderer/lib/shutdown-handler.js21
4 files changed, 39 insertions, 119 deletions
diff --git a/gui/packages/desktop/src/main/index.js b/gui/packages/desktop/src/main/index.js
index 50a4815544..4ba94831e7 100644
--- a/gui/packages/desktop/src/main/index.js
+++ b/gui/packages/desktop/src/main/index.js
@@ -10,7 +10,6 @@ import { app, screen, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'el
import TrayIconController from './tray-icon-controller';
import WindowController from './window-controller';
-import ShutdownCoordinator from './shutdown-coordinator';
import { DaemonRpc, ConnectionObserver, SubscriptionListener } from './daemon-rpc';
import type { TunnelStateTransition, Settings } from './daemon-rpc';
import ReconnectionBackoff from './reconnection-backoff';
@@ -21,6 +20,8 @@ import type { TrayIconType } from './tray-icon-controller';
const DAEMON_RPC_PATH =
process.platform === 'win32' ? '//./pipe/Mullvad VPN' : '/var/run/mullvad-vpn';
+type AppQuitStage = 'unready' | 'initiated' | 'ready';
+
const ApplicationMain = {
_windowController: (null: ?WindowController),
_trayIconController: (null: ?TrayIconController),
@@ -31,8 +32,7 @@ const ApplicationMain = {
_logFilePath: '',
_oldLogFilePath: (null: ?string),
- _connectionFilePollInterval: (null: ?IntervalID),
- _shouldQuit: false,
+ _quitStage: ('unready': AppQuitStage),
run() {
// Since electron's GPU blacklists are broken, GPU acceleration won't work on older distros
@@ -57,7 +57,7 @@ const ApplicationMain = {
app.on('activate', () => this._onActivate());
app.on('ready', () => this._onReady());
app.on('window-all-closed', () => app.quit());
- app.on('before-quit', () => this._onBeforeQuit());
+ app.on('before-quit', (event: Event) => this._onBeforeQuit(event));
const connectionObserver = new ConnectionObserver(
() => {
@@ -157,24 +157,48 @@ const ApplicationMain = {
}
},
- _onBeforeQuit() {
- this._shouldQuit = true;
+ async _onBeforeQuit(event: Event) {
+ switch (this._quitStage) {
+ case 'unready':
+ // postpone the app shutdown
+ event.preventDefault();
- if (process.env.NODE_ENV === 'development') {
- if (this._windowController) {
- this._windowController.window.closeDevTools();
- }
+ this._quitStage = 'initiated';
+ await this._prepareToQuit();
+
+ // terminate the app
+ this._quitStage = 'ready';
+ app.quit();
+ break;
+
+ case 'initiated':
+ // prevent immediate exit, the app will quit after running the shutdown routine
+ event.preventDefault();
+ return;
+
+ case 'ready':
+ // let the app quit freely at this point
+ break;
}
+ },
- return true;
+ async _prepareToQuit() {
+ if (this._connectedToDaemon) {
+ try {
+ await this._daemonRpc.disconnectTunnel();
+ log.info('Disconnected the tunnel');
+ } catch (e) {
+ log.error(`Failed to disconnect the tunnel: ${e.message}`);
+ }
+ } else {
+ log.info('Cannot close the tunnel because there is no active connection to daemon.');
+ }
},
async _onReady() {
const window = this._createWindow();
const tray = this._createTray();
- const _shutdownCoordinator = new ShutdownCoordinator(window.webContents);
-
const windowController = new WindowController(window, tray);
const trayIconController = new TrayIconController(tray, 'unsecured');
@@ -582,13 +606,10 @@ const ApplicationMain = {
},
_installLinuxWindowCloseHandler(windowController: WindowController) {
- windowController.window.on('close', (closeEvent) => {
- if (process.platform === 'linux' && !this._shouldQuit) {
+ windowController.window.on('close', (closeEvent: Event) => {
+ if (process.platform === 'linux' && this._quitStage !== 'ready') {
closeEvent.preventDefault();
windowController.hide();
- return false;
- } else {
- return true;
}
});
},
diff --git a/gui/packages/desktop/src/main/shutdown-coordinator.js b/gui/packages/desktop/src/main/shutdown-coordinator.js
deleted file mode 100644
index 3988d5a7a6..0000000000
--- a/gui/packages/desktop/src/main/shutdown-coordinator.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// @flow
-
-import log from 'electron-log';
-import { app, ipcMain } from 'electron';
-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.
- */
-export default 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();
- }
-}
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 0b9352a5c1..cd9d003659 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -15,7 +15,6 @@ import { createMemoryHistory } from 'history';
import { InvalidAccountError } from '../main/errors';
import makeRoutes from './routes';
import NotificationController from './lib/notification-controller';
-import setShutdownHandler from './lib/shutdown-handler';
import configureStore from './redux/store';
import accountActions from './redux/account/actions';
@@ -97,16 +96,6 @@ export default class AppRenderer {
),
};
- setShutdownHandler(async () => {
- log.info('Executing a shutdown handler');
- try {
- await this.disconnectTunnel();
- log.info('Disconnected the tunnel');
- } catch (e) {
- log.error(`Failed to disconnect the tunnel: ${e.message}`);
- }
- });
-
ipcRenderer.on('update-window-shape', (_event, shapeParams: WindowShapeParameters) => {
if (typeof shapeParams.arrowPosition === 'number') {
this._reduxActions.userInterface.updateWindowArrowPosition(shapeParams.arrowPosition);
diff --git a/gui/packages/desktop/src/renderer/lib/shutdown-handler.js b/gui/packages/desktop/src/renderer/lib/shutdown-handler.js
deleted file mode 100644
index 5570d24d28..0000000000
--- a/gui/packages/desktop/src/renderer/lib/shutdown-handler.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// @flow
-import log from 'electron-log';
-import { ipcRenderer } from 'electron';
-
-/**
- * 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).
- */
-export default 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');
- });
-}