Skip to content

Commit 344210b

Browse files
authored
Merge pull request microsoft#160643 from microsoft/ben/avoid-sync-fs
perf - avoid sync fs on startup
2 parents 4a2c156 + 2a74e7d commit 344210b

File tree

23 files changed

+299
-398
lines changed

23 files changed

+299
-398
lines changed

src/main.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ perf.mark('code/willStartCrashReporter');
5656
// * --disable-crash-reporter command line parameter is not set
5757
//
5858
// Disable crash reporting in all other cases.
59-
if (args['crash-reporter-directory'] ||
60-
(argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) {
59+
if (args['crash-reporter-directory'] || (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) {
6160
configureCrashReporter();
6261
}
6362
perf.mark('code/didStartCrashReporter');

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

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import { app, BrowserWindow, dialog, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron';
77
import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
8-
import { statSync } from 'fs';
98
import { hostname, release } from 'os';
109
import { VSBuffer } from 'vs/base/common/buffer';
1110
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -109,8 +108,7 @@ import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } fro
109108
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
110109
import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService';
111110
import { UserDataTransientProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataTransientProfilesHandler';
112-
import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
113-
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
111+
import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
114112

115113
/**
116114
* The main VS Code application. There will only ever be one instance,
@@ -327,12 +325,12 @@ export class CodeApplication extends Disposable {
327325
});
328326

329327
// macOS dock activate
330-
app.on('activate', (event, hasVisibleWindows) => {
328+
app.on('activate', async (event, hasVisibleWindows) => {
331329
this.logService.trace('app#activate');
332330

333331
// Mac only event: open new window when we get activated
334332
if (!hasVisibleWindows) {
335-
this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
333+
await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
336334
}
337335
});
338336

@@ -364,7 +362,7 @@ export class CodeApplication extends Disposable {
364362
event.preventDefault();
365363

366364
// Keep in array because more might come!
367-
macOpenFileURIs.push(this.getWindowOpenableFromPathSync(path));
365+
macOpenFileURIs.push(hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) });
368366

369367
// Clear previous handler if any
370368
if (runningTimeout !== undefined) {
@@ -373,8 +371,8 @@ export class CodeApplication extends Disposable {
373371
}
374372

375373
// Handle paths delayed in case more are coming!
376-
runningTimeout = setTimeout(() => {
377-
this.windowsMainService?.open({
374+
runningTimeout = setTimeout(async () => {
375+
await this.windowsMainService?.open({
378376
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
379377
cli: this.environmentMainService.args,
380378
urisToOpen: macOpenFileURIs,
@@ -387,8 +385,8 @@ export class CodeApplication extends Disposable {
387385
}, 100);
388386
});
389387

390-
app.on('new-window-for-tab', () => {
391-
this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
388+
app.on('new-window-for-tab', async () => {
389+
await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
392390
});
393391

394392
//#region Bootstrap IPC Handlers
@@ -538,15 +536,11 @@ export class CodeApplication extends Disposable {
538536
// Setup Handlers
539537
this.setUpHandlers(appInstantiationService);
540538

541-
// Ensure profile exists when passed in from CLI
542-
const profilePromise = this.userDataProfilesMainService.checkAndCreateProfileFromCli(this.environmentMainService.args);
543-
const profile = profilePromise ? await profilePromise : undefined;
544-
545539
// Init Channels
546540
appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient));
547541

548542
// Open Windows
549-
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, profile, mainProcessElectronServer));
543+
await appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer));
550544

551545
// Post Open Windows Tasks
552546
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess));
@@ -632,7 +626,8 @@ export class CodeApplication extends Disposable {
632626
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv], false));
633627

634628
// Dialogs
635-
services.set(IDialogMainService, new SyncDescriptor(DialogMainService, undefined, true));
629+
const dialogMainService = new DialogMainService(this.logService);
630+
services.set(IDialogMainService, dialogMainService);
636631

637632
// Launch
638633
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService, undefined, false /* proxied to other processes */));
@@ -659,10 +654,6 @@ export class CodeApplication extends Disposable {
659654
// Webview Manager
660655
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
661656

662-
// Workspaces
663-
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */));
664-
services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService, undefined, true));
665-
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false));
666657

667658
// Menubar
668659
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
@@ -690,6 +681,12 @@ export class CodeApplication extends Disposable {
690681
const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateMainService);
691682
services.set(IBackupMainService, backupMainService);
692683

684+
// Workspaces
685+
const workspacesManagementMainService = new WorkspacesManagementMainService(this.environmentMainService, this.logService, this.userDataProfilesMainService, backupMainService, dialogMainService, this.productService);
686+
services.set(IWorkspacesManagementMainService, workspacesManagementMainService);
687+
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */));
688+
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false));
689+
693690
// URL handling
694691
services.set(IURLService, new SyncDescriptor(NativeURLService, undefined, false /* proxied to other processes */));
695692

@@ -712,7 +709,10 @@ export class CodeApplication extends Disposable {
712709
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));
713710

714711
// Init services that require it
715-
await backupMainService.initialize();
712+
await Promises.settled([
713+
backupMainService.initialize(),
714+
workspacesManagementMainService.initialize()
715+
]);
716716

717717
return this.mainInstantiationService.createChild(services);
718718
}
@@ -829,7 +829,7 @@ export class CodeApplication extends Disposable {
829829
mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel);
830830
}
831831

832-
private openFirstWindow(accessor: ServicesAccessor, profile: IUserDataProfile | undefined, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] {
832+
private async openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): Promise<ICodeWindow[]> {
833833
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
834834
const urlService = accessor.get(IURLService);
835835
const nativeHostMainService = accessor.get(INativeHostMainService);
@@ -917,7 +917,7 @@ export class CodeApplication extends Disposable {
917917
const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
918918
logService.trace('app#handleURL: windowOpenableFromProtocolLink = ', windowOpenableFromProtocolLink);
919919
if (windowOpenableFromProtocolLink) {
920-
const [window] = windowsMainService.open({
920+
const [window] = await windowsMainService.open({
921921
context: OpenContext.API,
922922
cli: { ...environmentService.args },
923923
urisToOpen: [windowOpenableFromProtocolLink],
@@ -932,7 +932,7 @@ export class CodeApplication extends Disposable {
932932
}
933933

934934
if (shouldOpenInNewWindow) {
935-
const [window] = windowsMainService.open({
935+
const [window] = await windowsMainService.open({
936936
context: OpenContext.API,
937937
cli: { ...environmentService.args },
938938
forceNewWindow: true,
@@ -989,6 +989,9 @@ export class CodeApplication extends Disposable {
989989
});
990990
}
991991

992+
// Ensure profile exists when passed in from CLI
993+
const profile = await this.userDataProfilesMainService.checkAndCreateProfileFromCli(this.environmentMainService.args);
994+
992995
// Start without file/folder arguments
993996
if (!hasCliArgs && !hasFolderURIs && !hasFileURIs) {
994997

@@ -1012,7 +1015,7 @@ export class CodeApplication extends Disposable {
10121015
return windowsMainService.open({
10131016
context: OpenContext.DOCK,
10141017
cli: args,
1015-
urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
1018+
urisToOpen: macOpenFiles.map(path => (hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) })),
10161019
noRecentEntry,
10171020
waitMarkerFileURI,
10181021
initialStartup: true,
@@ -1104,23 +1107,6 @@ export class CodeApplication extends Disposable {
11041107
return undefined;
11051108
}
11061109

1107-
private getWindowOpenableFromPathSync(path: string): IWindowOpenable {
1108-
try {
1109-
const fileStat = statSync(path);
1110-
if (fileStat.isDirectory()) {
1111-
return { folderUri: URI.file(path) };
1112-
}
1113-
1114-
if (hasWorkspaceFileExtension(path)) {
1115-
return { workspaceUri: URI.file(path) };
1116-
}
1117-
} catch (error) {
1118-
// ignore errors
1119-
}
1120-
1121-
return { fileUri: URI.file(path) };
1122-
}
1123-
11241110
private afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): void {
11251111
const telemetryService = accessor.get(ITelemetryService);
11261112
const updateService = accessor.get(IUpdateService);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsServ
3434
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
3535
import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
3636
import { addArg, parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper';
37-
import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
37+
import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
3838
import { IFileService } from 'vs/platform/files/common/files';
3939
import { FileService } from 'vs/platform/files/common/fileService';
4040
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
@@ -469,8 +469,9 @@ class CodeMain {
469469
//
470470
// Note: we are not doing this if the wait marker has been already
471471
// added as argument. This can happen if Code was started from CLI.
472+
472473
if (args.wait && !args.waitMarkerFilePath) {
473-
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
474+
const waitMarkerFilePath = createWaitMarkerFileSync(args.verbose);
474475
if (waitMarkerFilePath) {
475476
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
476477
args.waitMarkerFilePath = waitMarkerFilePath;

src/vs/code/node/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
1919
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
2020
import { addArg, parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
2121
import { getStdinFilePath, hasStdinWithoutTty, readFromStdin, stdinDataListener } from 'vs/platform/environment/node/stdin';
22-
import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
22+
import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
2323
import product from 'vs/platform/product/common/product';
2424
import { CancellationTokenSource } from 'vs/base/common/cancellation';
2525
import { randomPath } from 'vs/base/common/extpath';
@@ -220,7 +220,7 @@ export async function main(argv: string[]): Promise<any> {
220220
// is closed and then exit the waiting process.
221221
let waitMarkerFilePath: string | undefined;
222222
if (args.wait) {
223-
waitMarkerFilePath = createWaitMarkerFile(verbose);
223+
waitMarkerFilePath = createWaitMarkerFileSync(verbose);
224224
if (waitMarkerFilePath) {
225225
addArg(argv, '--waitMarkerFilePath', waitMarkerFilePath);
226226
}

src/vs/platform/backup/electron-main/backup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export interface IBackupMainService {
1717

1818
getEmptyWindowBackups(): IEmptyWindowBackupInfo[];
1919

20-
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string;
20+
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo): string;
21+
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom: string): Promise<string>;
2122
registerFolderBackup(folderInfo: IFolderBackupInfo): string;
2223
registerEmptyWindowBackup(emptyWindowInfo: IEmptyWindowBackupInfo): string;
2324

src/vs/platform/backup/electron-main/backupMainService.ts

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { createHash } from 'crypto';
7-
import * as fs from 'fs';
87
import { isEqual } from 'vs/base/common/extpath';
98
import { Schemas } from 'vs/base/common/network';
109
import { join } from 'vs/base/common/path';
@@ -134,7 +133,9 @@ export class BackupMainService implements IBackupMainService {
134133
return this.emptyWindows.slice(0); // return a copy
135134
}
136135

137-
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string {
136+
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo): string;
137+
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom: string): Promise<string>;
138+
registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string | Promise<string> {
138139
if (!this.workspaces.some(workspace => workspaceInfo.workspace.id === workspace.workspace.id)) {
139140
this.workspaces.push(workspaceInfo);
140141
this.storeWorkspacesMetadata();
@@ -143,23 +144,23 @@ export class BackupMainService implements IBackupMainService {
143144
const backupPath = join(this.backupHome, workspaceInfo.workspace.id);
144145

145146
if (migrateFrom) {
146-
this.moveBackupFolderSync(backupPath, migrateFrom);
147+
return this.moveBackupFolder(backupPath, migrateFrom).then(() => backupPath);
147148
}
148149

149150
return backupPath;
150151
}
151152

152-
private moveBackupFolderSync(backupPath: string, moveFromPath: string): void {
153+
private async moveBackupFolder(backupPath: string, moveFromPath: string): Promise<void> {
153154

154155
// Target exists: make sure to convert existing backups to empty window backups
155-
if (fs.existsSync(backupPath)) {
156-
this.convertToEmptyWindowBackupSync(backupPath);
156+
if (await Promises.exists(backupPath)) {
157+
await this.convertToEmptyWindowBackup(backupPath);
157158
}
158159

159160
// When we have data to migrate from, move it over to the target location
160-
if (fs.existsSync(moveFromPath)) {
161+
if (await Promises.exists(moveFromPath)) {
161162
try {
162-
fs.renameSync(moveFromPath, backupPath);
163+
await Promises.rename(moveFromPath, backupPath);
163164
} catch (error) {
164165
this.logService.error(`Backup: Could not move backup folder to new location: ${error.toString()}`);
165166
}
@@ -324,22 +325,6 @@ export class BackupMainService implements IBackupMainService {
324325
return true;
325326
}
326327

327-
private convertToEmptyWindowBackupSync(backupPath: string): boolean {
328-
const newEmptyWindowBackupInfo = this.prepareNewEmptyWindowBackup();
329-
330-
// Rename backupPath to new empty window backup path
331-
const newEmptyWindowBackupPath = join(this.backupHome, newEmptyWindowBackupInfo.backupFolder);
332-
try {
333-
fs.renameSync(backupPath, newEmptyWindowBackupPath);
334-
} catch (error) {
335-
this.logService.error(`Backup: Could not rename backup folder: ${error.toString()}`);
336-
return false;
337-
}
338-
this.emptyWindows.push(newEmptyWindowBackupInfo);
339-
340-
return true;
341-
}
342-
343328
async getDirtyWorkspaces(): Promise<Array<IWorkspaceBackupInfo | IFolderBackupInfo>> {
344329
const dirtyWorkspaces: Array<IWorkspaceBackupInfo | IFolderBackupInfo> = [];
345330

src/vs/platform/backup/test/electron-main/backupMainService.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,13 @@ flakySuite('BackupMainService', () => {
328328
assert.strictEqual(service.getEmptyWindowBackups().length, 1);
329329
});
330330

331-
test('service supports to migrate backup data from another location', () => {
331+
test('service supports to migrate backup data from another location', async () => {
332332
const backupPathToMigrate = service.toBackupPath(fooFile);
333333
fs.mkdirSync(backupPathToMigrate);
334334
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
335335
service.registerFolderBackup(toFolderBackupInfo(URI.file(backupPathToMigrate)));
336336

337-
const workspaceBackupPath = service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
337+
const workspaceBackupPath = await service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
338338

339339
assert.ok(fs.existsSync(workspaceBackupPath));
340340
assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));
@@ -344,7 +344,7 @@ flakySuite('BackupMainService', () => {
344344
assert.strictEqual(0, emptyBackups.length);
345345
});
346346

347-
test('service backup migration makes sure to preserve existing backups', () => {
347+
test('service backup migration makes sure to preserve existing backups', async () => {
348348
const backupPathToMigrate = service.toBackupPath(fooFile);
349349
fs.mkdirSync(backupPathToMigrate);
350350
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
@@ -355,7 +355,7 @@ flakySuite('BackupMainService', () => {
355355
fs.writeFileSync(path.join(backupPathToPreserve, 'backup.txt'), 'Some Data');
356356
service.registerFolderBackup(toFolderBackupInfo(URI.file(backupPathToPreserve)));
357357

358-
const workspaceBackupPath = service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
358+
const workspaceBackupPath = await service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
359359

360360
assert.ok(fs.existsSync(workspaceBackupPath));
361361
assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));

src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,10 @@ export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends Extens
3636
return { success: false };
3737
}
3838

39-
// Ensure profile exists when passed in from args
40-
const profilePromise = this.userDataProfilesMainService.checkAndCreateProfileFromCli(pargs);
41-
const profile = profilePromise ? await profilePromise : undefined;
42-
43-
const [codeWindow] = this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
39+
const [codeWindow] = await this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
4440
context: OpenContext.API,
4541
cli: pargs,
46-
profile
42+
profile: await this.userDataProfilesMainService.checkAndCreateProfileFromCli(pargs)
4743
});
4844

4945
if (!debugRenderer) {

0 commit comments

Comments
 (0)