diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-11-14 18:03:52 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-11-15 11:24:19 +0100 |
| commit | 8cc8423ac343893e7d622efe665b8083d9b9fc0f (patch) | |
| tree | 6ae7781c6dc1d2fe2d10b21666e8c1f982f026c0 | |
| parent | 7fceec2f4684ef6975576ac85842ef0377ce1a3d (diff) | |
| download | mullvadvpn-8cc8423ac343893e7d622efe665b8083d9b9fc0f.tar.xz mullvadvpn-8cc8423ac343893e7d622efe665b8083d9b9fc0f.zip | |
Move shutdown handling to the main process
| -rw-r--r-- | gui/packages/desktop/src/main/index.js | 57 | ||||
| -rw-r--r-- | gui/packages/desktop/src/main/shutdown-coordinator.js | 69 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/app.js | 11 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/lib/shutdown-handler.js | 21 |
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'); - }); -} |
