Skip to content

Commit 09b6468

Browse files
author
Benjamin Pasero
committed
introduce shutdown reason (fixes microsoft#15509)
1 parent 4721762 commit 09b6468

9 files changed

Lines changed: 76 additions & 42 deletions

File tree

src/vs/code/electron-main/lifecycle.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
1616

1717
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
1818

19+
export enum UnloadReason {
20+
CLOSE,
21+
QUIT,
22+
RELOAD,
23+
LOAD
24+
}
25+
1926
export interface ILifecycleService {
2027
_serviceBrand: any;
2128

@@ -33,7 +40,7 @@ export interface ILifecycleService {
3340

3441
ready(): void;
3542
registerWindow(vscodeWindow: IVSCodeWindow): void;
36-
unload(vscodeWindow: IVSCodeWindow): TPromise<boolean /* veto */>;
43+
unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise<boolean /* veto */>;
3744
quit(fromUpdate?: boolean): TPromise<boolean /* veto */>;
3845
}
3946

@@ -126,7 +133,7 @@ export class LifecycleService implements ILifecycleService {
126133

127134
// Otherwise prevent unload and handle it from window
128135
e.preventDefault();
129-
this.unload(vscodeWindow).done(veto => {
136+
this.unload(vscodeWindow, UnloadReason.CLOSE).done(veto => {
130137
if (!veto) {
131138
this.windowToCloseRequest[windowId] = true;
132139
vscodeWindow.win.close();
@@ -138,7 +145,7 @@ export class LifecycleService implements ILifecycleService {
138145
});
139146
}
140147

141-
public unload(vscodeWindow: IVSCodeWindow): TPromise<boolean /* veto */> {
148+
public unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
142149

143150
// Always allow to unload a window that is not yet ready
144151
if (vscodeWindow.readyState !== ReadyState.READY) {
@@ -149,14 +156,14 @@ export class LifecycleService implements ILifecycleService {
149156

150157
return new TPromise<boolean>((c) => {
151158
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
152-
const oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken;
153-
const oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken;
159+
const okChannel = `vscode:ok${oneTimeEventToken}`;
160+
const cancelChannel = `vscode:cancel${oneTimeEventToken}`;
154161

155-
ipc.once(oneTimeOkEvent, () => {
162+
ipc.once(okChannel, () => {
156163
c(false); // no veto
157164
});
158165

159-
ipc.once(oneTimeCancelEvent, () => {
166+
ipc.once(cancelChannel, () => {
160167

161168
// Any cancellation also cancels a pending quit if present
162169
if (this.pendingQuitPromiseComplete) {
@@ -168,7 +175,7 @@ export class LifecycleService implements ILifecycleService {
168175
c(true); // veto
169176
});
170177

171-
vscodeWindow.send('vscode:beforeUnload', { okChannel: oneTimeOkEvent, cancelChannel: oneTimeCancelEvent, quitRequested: this.quitRequested });
178+
vscodeWindow.send('vscode:beforeUnload', { okChannel, cancelChannel, reason: this.quitRequested ? UnloadReason.QUIT : reason });
172179
});
173180
}
174181

src/vs/code/electron-main/windows.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { IStorageService } from 'vs/code/electron-main/storage';
2020
import { IPath, VSCodeWindow, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, ReadyState } from 'vs/code/electron-main/window';
2121
import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron';
2222
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths';
23-
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
23+
import { ILifecycleService, UnloadReason } from 'vs/code/electron-main/lifecycle';
2424
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2525
import { ILogService } from 'vs/code/electron-main/log';
2626
import { getPathLabel } from 'vs/base/common/labels';
@@ -273,7 +273,7 @@ export class WindowsManager implements IWindowsMainService {
273273
public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
274274

275275
// Only reload when the window has not vetoed this
276-
this.lifecycleService.unload(win).done(veto => {
276+
this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
277277
if (!veto) {
278278
win.reload(cli);
279279
}
@@ -762,7 +762,7 @@ export class WindowsManager implements IWindowsMainService {
762762
}
763763

764764
// Only load when the window has not vetoed this
765-
this.lifecycleService.unload(vscodeWindow).done(veto => {
765+
this.lifecycleService.unload(vscodeWindow, UnloadReason.LOAD).done(veto => {
766766
if (!veto) {
767767

768768
// Load it

src/vs/platform/lifecycle/common/lifecycle.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,22 @@ export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleSe
2020
*/
2121
export interface ShutdownEvent {
2222
veto(value: boolean | TPromise<boolean>): void;
23-
quitRequested: boolean;
23+
reason: ShutdownReason;
24+
}
25+
26+
export enum ShutdownReason {
27+
28+
/** Window is closed */
29+
CLOSE,
30+
31+
/** Application is quit */
32+
QUIT,
33+
34+
/** Window is reloaded */
35+
RELOAD,
36+
37+
/** Other configuration loaded into window */
38+
LOAD
2439
}
2540

2641
/**
@@ -37,12 +52,6 @@ export interface ILifecycleService {
3752
*/
3853
willShutdown: boolean;
3954

40-
/**
41-
* A flag indications if the application is in the process of quitting all windows. This will be
42-
* set before the onWillShutdown event is fired and reverted to false afterwards.
43-
*/
44-
quitRequested: boolean;
45-
4655
/**
4756
* Fired before shutdown happens. Allows listeners to veto against the
4857
* shutdown.
@@ -59,7 +68,6 @@ export interface ILifecycleService {
5968
export const NullLifecycleService: ILifecycleService = {
6069
_serviceBrand: null,
6170
willShutdown: false,
62-
quitRequested: false,
6371
onWillShutdown: () => ({ dispose() { } }),
6472
onShutdown: () => ({ dispose() { } })
6573
};

src/vs/test/utils/servicesTestUtils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,6 @@ export class TestLifecycleService implements ILifecycleService {
708708
public _serviceBrand: any;
709709

710710
public willShutdown: boolean;
711-
public quitRequested: boolean;
712711

713712
private _onWillShutdown = new Emitter<ShutdownEvent>();
714713
private _onShutdown = new Emitter<void>();

src/vs/workbench/services/backup/common/backup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
1010
import { TPromise } from 'vs/base/common/winjs.base';
1111
import { ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles';
1212
import { IResolveContentOptions, IUpdateContentOptions } from 'vs/platform/files/common/files';
13+
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
1314

1415
export const IBackupService = createDecorator<IBackupService>('backupService');
1516
export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService');
@@ -29,7 +30,7 @@ export interface IBackupService {
2930
_serviceBrand: any;
3031

3132
isHotExitEnabled: boolean;
32-
backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise<IBackupResult>;
33+
backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult>;
3334
cleanupBackupsBeforeShutdown(): TPromise<void>;
3435
}
3536

src/vs/workbench/services/backup/node/backupService.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
1717
import { TPromise } from 'vs/base/common/winjs.base';
1818
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
1919
import { IWindowsService } from 'vs/platform/windows/common/windows';
20+
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
2021

2122
export class BackupService implements IBackupService {
2223

@@ -95,21 +96,46 @@ export class BackupService implements IBackupService {
9596
return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace();
9697
}
9798

98-
public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise<IBackupResult> {
99+
public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult> {
99100
if (!this.isHotExitEnabled) {
100101
return TPromise.as({ didBackup: false });
101102
}
102103

103104
return this.windowsService.getWindowCount().then(windowCount => {
105+
104106
// When quit is requested skip the confirm callback and attempt to backup all workspaces.
105107
// When quit is not requested the confirm callback should be shown when the window being
106108
// closed is the only VS Code window open, except for on Mac where hot exit is only
107109
// ever activated when quit is requested.
108-
if (!quitRequested && (windowCount > 1 || platform.isMacintosh)) {
110+
111+
let doBackup: boolean;
112+
switch (reason) {
113+
case ShutdownReason.CLOSE:
114+
if (windowCount > 1 || platform.isMacintosh) {
115+
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
116+
} else {
117+
doBackup = true; // backup if last window is closed on win/linux where the application quits right after
118+
}
119+
break;
120+
121+
case ShutdownReason.QUIT:
122+
doBackup = true; // backup because next start we restore all backups
123+
break;
124+
125+
case ShutdownReason.RELOAD:
126+
doBackup = true; // backup because after window reload, backups restore
127+
break;
128+
129+
case ShutdownReason.LOAD:
130+
doBackup = false; // do not backup because we are switching contexts
131+
break;
132+
}
133+
134+
if (!doBackup) {
109135
return TPromise.as({ didBackup: false });
110136
}
111137

112-
// Backup and hot exit
138+
// Backup
113139
return this.backupAll(dirtyToBackup, textFileEditorModelManager).then(() => { return { didBackup: true }; }); // we did backup
114140
});
115141
}

src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { TPromise } from 'vs/base/common/winjs.base';
88
import Severity from 'vs/base/common/severity';
99
import { toErrorMessage } from 'vs/base/common/errorMessage';
10-
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
10+
import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
1111
import { IMessageService } from 'vs/platform/message/common/message';
1212
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
1313
import { ipcRenderer as ipc } from 'electron';
@@ -21,7 +21,6 @@ export class LifecycleService implements ILifecycleService {
2121
private _onShutdown = new Emitter<void>();
2222

2323
private _willShutdown: boolean;
24-
private _quitRequested: boolean;
2524

2625
constructor(
2726
@IMessageService private messageService: IMessageService,
@@ -34,10 +33,6 @@ export class LifecycleService implements ILifecycleService {
3433
return this._willShutdown;
3534
}
3635

37-
public get quitRequested(): boolean {
38-
return this._quitRequested;
39-
}
40-
4136
public get onWillShutdown(): Event<ShutdownEvent> {
4237
return this._onWillShutdown.event;
4338
}
@@ -50,13 +45,11 @@ export class LifecycleService implements ILifecycleService {
5045
const windowId = this.windowService.getWindowId();
5146

5247
// Main side indicates that window is about to unload, check for vetos
53-
ipc.on('vscode:beforeUnload', (event, reply: { okChannel: string, cancelChannel: string, quitRequested: boolean }) => {
48+
ipc.on('vscode:beforeUnload', (event, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => {
5449
this._willShutdown = true;
55-
this._quitRequested = reply.quitRequested;
5650

5751
// trigger onWillShutdown events and veto collecting
58-
this.onBeforeUnload(reply.quitRequested).done(veto => {
59-
this._quitRequested = false;
52+
this.onBeforeUnload(reply.reason).done(veto => {
6053
if (veto) {
6154
this._willShutdown = false; // reset this flag since the shutdown has been vetoed!
6255
ipc.send(reply.cancelChannel, windowId);
@@ -68,14 +61,14 @@ export class LifecycleService implements ILifecycleService {
6861
});
6962
}
7063

71-
private onBeforeUnload(quitRequested: boolean): TPromise<boolean> {
64+
private onBeforeUnload(reason: ShutdownReason): TPromise<boolean> {
7265
const vetos: (boolean | TPromise<boolean>)[] = [];
7366

7467
this._onWillShutdown.fire({
7568
veto(value) {
7669
vetos.push(value);
7770
},
78-
quitRequested
71+
reason
7972
});
8073

8174
if (vetos.length === 0) {

src/vs/workbench/services/textfile/browser/textFileService.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import objects = require('vs/base/common/objects');
1414
import Event, { Emitter } from 'vs/base/common/event';
1515
import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
1616
import { ConfirmResult } from 'vs/workbench/common/editor';
17-
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
17+
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
1818
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
1919
import { IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration } from 'vs/platform/files/common/files';
2020
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -101,7 +101,7 @@ export abstract class TextFileService implements ITextFileService {
101101
private registerListeners(): void {
102102

103103
// Lifecycle
104-
this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.quitRequested)));
104+
this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.reason)));
105105
this.lifecycleService.onShutdown(this.dispose, this);
106106

107107
// Configuration changes
@@ -113,7 +113,7 @@ export abstract class TextFileService implements ITextFileService {
113113
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged()));
114114
}
115115

116-
private beforeShutdown(quitRequested: boolean): boolean | TPromise<boolean> {
116+
private beforeShutdown(reason: ShutdownReason): boolean | TPromise<boolean> {
117117

118118
// Dirty files need treatment on shutdown
119119
const dirty = this.getDirty();
@@ -135,7 +135,7 @@ export abstract class TextFileService implements ITextFileService {
135135

136136
// If hot exit is enabled, backup dirty files and allow to exit without confirmation
137137
if (this.backupService.isHotExitEnabled) {
138-
return this.backupService.backupBeforeShutdown(dirty, this.models, quitRequested).then(result => {
138+
return this.backupService.backupBeforeShutdown(dirty, this.models, reason).then(result => {
139139
if (result.didBackup) {
140140
return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful)
141141
}

src/vs/workbench/services/textfile/test/textFileService.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { TPromise } from 'vs/base/common/winjs.base';
88
import * as assert from 'assert';
9-
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
9+
import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
1010
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, onError, toResource } from 'vs/test/utils/servicesTestUtils';
1111
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1212
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
@@ -28,7 +28,7 @@ class ServiceAccessor {
2828
class ShutdownEventImpl implements ShutdownEvent {
2929

3030
public value: boolean | TPromise<boolean>;
31-
public quitRequested: boolean = false;
31+
public reason = ShutdownReason.CLOSE;
3232

3333
veto(value: boolean | TPromise<boolean>): void {
3434
this.value = value;

0 commit comments

Comments
 (0)