Skip to content

Commit 07eb348

Browse files
author
Benjamin Pasero
committed
debt - introduce lifecycle phases on electron-main and adopt
1 parent ca35e2c commit 07eb348

17 files changed

Lines changed: 375 additions & 300 deletions

File tree

src/vs/base/node/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
109109
try {
110110
this.parseErrors = [];
111111
res = this.options.parse ? this.options.parse(raw, this.parseErrors) : json.parse(raw, this.parseErrors);
112+
112113
return res || this.options.defaultConfig;
113114
} catch (error) {
114-
// Ignore parsing errors
115-
return this.options.defaultConfig;
115+
return this.options.defaultConfig; // Ignore parsing errors
116116
}
117117
}
118118

src/vs/code/code.main.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

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

Lines changed: 164 additions & 186 deletions
Large diffs are not rendered by default.

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

Lines changed: 0 additions & 32 deletions
This file was deleted.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ interface PostResult {
2222

2323
class Endpoint {
2424
private constructor(
25-
public readonly url: string
25+
readonly url: string
2626
) { }
2727

28-
public static getFromProduct(): Endpoint | undefined {
28+
static getFromProduct(): Endpoint | undefined {
2929
const logUploaderUrl = product.logUploaderUrl;
3030
return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined;
3131
}

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

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import 'vs/code/code.main';
6+
import 'vs/platform/update/node/update.config.contribution';
77
import { app, dialog } from 'electron';
88
import { assign } from 'vs/base/common/objects';
99
import * as platform from 'vs/base/common/platform';
@@ -39,6 +39,7 @@ import { uploadLogs } from 'vs/code/electron-main/logUploader';
3939
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
4040
import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
4141
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
42+
import { once } from 'vs/base/common/functional';
4243

4344
class ExpectedError extends Error {
4445
readonly isExpected = true;
@@ -90,17 +91,14 @@ class CodeMain {
9091
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
9192
const bufferLogService = new BufferLogService();
9293

93-
const instantiationService = this.createServices(args, bufferLogService);
94+
const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService);
9495
try {
96+
97+
// Init services
9598
await instantiationService.invokeFunction(async accessor => {
9699
const environmentService = accessor.get(IEnvironmentService);
97100
const stateService = accessor.get(IStateService);
98-
const logService = accessor.get(ILogService);
99-
100-
// Patch `process.env` with the instance's environment
101-
const instanceEnvironment = this.patchEnvironment(environmentService);
102101

103-
// Startup
104102
try {
105103
await this.initServices(environmentService, stateService as StateService);
106104
} catch (error) {
@@ -110,9 +108,19 @@ class CodeMain {
110108

111109
throw error;
112110
}
111+
});
112+
113+
// Startup
114+
await instantiationService.invokeFunction(async accessor => {
115+
const environmentService = accessor.get(IEnvironmentService);
116+
const logService = accessor.get(ILogService);
117+
const lifecycleService = accessor.get(ILifecycleService);
118+
const configurationService = accessor.get(IConfigurationService);
119+
120+
const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);
113121

114-
const mainIpcServer = await this.doStartup(logService, environmentService, instantiationService, true);
115122
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
123+
once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose());
116124

117125
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
118126
});
@@ -121,29 +129,30 @@ class CodeMain {
121129
}
122130
}
123131

124-
private createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
132+
private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] {
125133
const services = new ServiceCollection();
126134

127135
const environmentService = new EnvironmentService(args, process.execPath);
136+
const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
137+
services.set(IEnvironmentService, environmentService);
128138

129139
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
130140
process.once('exit', () => logService.dispose());
131-
132-
services.set(IEnvironmentService, environmentService);
133141
services.set(ILogService, logService);
142+
134143
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
135144
services.set(IStateService, new SyncDescriptor(StateService));
136145
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
137146
services.set(IRequestService, new SyncDescriptor(RequestService));
138147
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
139148
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
140149

141-
return new InstantiationService(services, true);
150+
return [new InstantiationService(services, true), instanceEnvironment];
142151
}
143152

144153
private initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<unknown> {
145154

146-
// Ensure paths for environment service exist
155+
// Environment service (paths)
147156
const environmentServiceInitialization = Promise.all<void | undefined>([
148157
environmentService.extensionsPath,
149158
environmentService.nodeCachedDataDir,
@@ -175,14 +184,15 @@ class CodeMain {
175184
return instanceEnvironment;
176185
}
177186

178-
private async doStartup(logService: ILogService, environmentService: IEnvironmentService, instantiationService: IInstantiationService, retry: boolean): Promise<Server> {
187+
private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleService, instantiationService: IInstantiationService, retry: boolean): Promise<Server> {
179188

180189
// Try to setup a server for running. If that succeeds it means
181190
// we are the first instance to startup. Otherwise it is likely
182191
// that another instance is already running.
183192
let server: Server;
184193
try {
185194
server = await serve(environmentService.mainIPCHandle);
195+
once(lifecycleService.onWillShutdown)(() => server.dispose());
186196
} catch (error) {
187197

188198
// Handle unexpected errors (the only expected error is EADDRINUSE that
@@ -226,10 +236,11 @@ class CodeMain {
226236
fs.unlinkSync(environmentService.mainIPCHandle);
227237
} catch (error) {
228238
logService.warn('Could not delete obsolete instance handle', error);
239+
229240
throw error;
230241
}
231242

232-
return this.doStartup(logService, environmentService, instantiationService, false);
243+
return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false);
233244
}
234245

235246
// Tests from CLI require to be the only instance currently
@@ -261,8 +272,8 @@ class CodeMain {
261272
if (environmentService.args.status) {
262273
return instantiationService.invokeFunction(async accessor => {
263274
const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(launchClient);
264-
265275
console.log(diagnostics);
276+
266277
throw new ExpectedError();
267278
});
268279
}
@@ -300,12 +311,14 @@ class CodeMain {
300311
// Print --status usage info
301312
if (environmentService.args.status) {
302313
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
314+
303315
throw new ExpectedError('Terminating...');
304316
}
305317

306318
// Log uploader usage info
307319
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
308320
logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.');
321+
309322
throw new ExpectedError('Terminating...');
310323
}
311324

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
203203
return !!this.config.extensionTestsPath;
204204
}
205205

206-
/*
207-
get extensionDevelopmentPaths(): string | string[] | undefined {
208-
return this.config.extensionDevelopmentPath;
209-
}
210-
*/
211-
212206
get config(): IWindowConfiguration {
213207
return this.currentConfig;
214208
}

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

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
1515
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
1616
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron';
1717
import { parseLineAndColumnAware } from 'vs/code/node/paths';
18-
import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
18+
import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
1919
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2020
import { ILogService } from 'vs/platform/log/common/log';
2121
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
@@ -38,6 +38,7 @@ import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename
3838
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
3939
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
4040
import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService';
41+
import { once } from 'vs/base/common/functional';
4142

4243
const enum WindowError {
4344
UNRESPONSIVE = 1,
@@ -159,9 +160,7 @@ export class WindowsManager implements IWindowsMainService {
159160

160161
private static readonly windowsStateStorageKey = 'windowsState';
161162

162-
private static WINDOWS: ICodeWindow[] = [];
163-
164-
private initialUserEnv: IProcessEnvironment;
163+
private static readonly WINDOWS: ICodeWindow[] = [];
165164

166165
private readonly windowsState: IWindowsState;
167166
private lastClosedWindowState?: IWindowState;
@@ -170,19 +169,20 @@ export class WindowsManager implements IWindowsMainService {
170169
private readonly workspacesManager: WorkspacesManager;
171170

172171
private _onWindowReady = new Emitter<ICodeWindow>();
173-
onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
172+
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
174173

175174
private _onWindowClose = new Emitter<number>();
176-
onWindowClose: CommonEvent<number> = this._onWindowClose.event;
175+
readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event;
177176

178177
private _onWindowLoad = new Emitter<number>();
179-
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
178+
readonly onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
180179

181180
private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
182-
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
181+
readonly onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
183182

184183
constructor(
185184
private readonly machineId: string,
185+
private readonly initialUserEnv: IProcessEnvironment,
186186
@ILogService private readonly logService: ILogService,
187187
@IStateService private readonly stateService: IStateService,
188188
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@@ -203,12 +203,50 @@ export class WindowsManager implements IWindowsMainService {
203203

204204
this.dialogs = new Dialogs(stateService, this);
205205
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
206+
207+
this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
208+
this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.setupNativeHelpers());
206209
}
207210

208-
ready(initialUserEnv: IProcessEnvironment): void {
209-
this.initialUserEnv = initialUserEnv;
211+
private setupNativeHelpers(): void {
212+
if (isWindows) {
213+
214+
// Setup Windows mutex
215+
try {
216+
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
217+
const mutex = new WindowsMutex(product.win32MutexName);
218+
once(this.lifecycleService.onWillShutdown)(() => mutex.release());
219+
} catch (e) {
220+
this.logService.error(e);
221+
222+
if (!this.environmentService.isBuilt) {
223+
this.showMessageBox({
224+
title: product.nameLong,
225+
type: 'warning',
226+
message: 'Failed to load windows-mutex!',
227+
detail: e.toString(),
228+
noLink: true
229+
});
230+
}
231+
}
210232

211-
this.registerListeners();
233+
// Dev only: Ensure Windows foreground love module is present
234+
if (!this.environmentService.isBuilt) {
235+
try {
236+
require.__$__nodeRequire('windows-foreground-love');
237+
} catch (e) {
238+
this.logService.error(e);
239+
240+
this.showMessageBox({
241+
title: product.nameLong,
242+
type: 'warning',
243+
message: 'Failed to load windows-foreground-love!',
244+
detail: e.toString(),
245+
noLink: true
246+
});
247+
}
248+
}
249+
}
212250
}
213251

214252
private registerListeners(): void {
@@ -543,6 +581,7 @@ export class WindowsManager implements IWindowsMainService {
543581

544582
// Find suitable window or folder path to open files in
545583
const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0];
584+
546585
// only look at the windows with correct authority
547586
const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority);
548587

@@ -639,7 +678,6 @@ export class WindowsManager implements IWindowsMainService {
639678

640679
// Handle folders to open (instructed and to restore)
641680
const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates
642-
643681
if (allFoldersToOpen.length > 0) {
644682

645683
// Check for existing instances
@@ -713,7 +751,9 @@ export class WindowsManager implements IWindowsMainService {
713751
if (fileInputs && !emptyToOpen) {
714752
emptyToOpen++;
715753
}
754+
716755
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
756+
717757
for (let i = 0; i < emptyToOpen; i++) {
718758
usedWindows.push(this.openInBrowserWindow({
719759
userEnv: openConfig.userEnv,

src/vs/code/node/cli.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export async function main(argv: string[]): Promise<any> {
5757
else if (shouldSpawnCliProcess(args)) {
5858
const cli = await new Promise<IMainCli>((c, e) => require(['vs/code/node/cliProcessMain'], c, e));
5959
await cli.main(args);
60+
6061
return;
6162
}
6263

src/vs/platform/environment/node/environmentService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ export function parseDebugPort(debugArg: string | undefined, debugBrkArg: string
287287
const portStr = debugBrkArg || debugArg;
288288
const port = Number(portStr) || (!isBuild ? defaultBuildPort : null);
289289
const brk = port ? Boolean(!!debugBrkArg) : false;
290+
290291
return { port, break: brk, debugId };
291292
}
292293

@@ -301,9 +302,9 @@ function parsePathArg(arg: string | undefined, process: NodeJS.Process): string
301302

302303
if (path.normalize(arg) === resolved) {
303304
return resolved;
304-
} else {
305-
return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg);
306305
}
306+
307+
return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg);
307308
}
308309

309310
export function parseUserDataDir(args: ParsedArgs, process: NodeJS.Process): string {

0 commit comments

Comments
 (0)