💄 - introduce CodeMain

This commit is contained in:
Benjamin Pasero 2019-05-28 08:33:32 +02:00
parent bda5098e02
commit edd560d278
2 changed files with 197 additions and 194 deletions

View file

@ -358,15 +358,16 @@ export class CodeApplication extends Disposable {
}
private async resolveMachineId(): Promise<string> {
// We cache the machineId for faster lookups on startup
// and resolve it only once initially if not cached
let machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
return machineId;
if (!machineId) {
machineId = await getMachineId();
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
}
machineId = await getMachineId();
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
return machineId;
}
@ -462,7 +463,6 @@ export class CodeApplication extends Disposable {
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
return Promise.resolve();
}
private initBackupService(accessor: ServicesAccessor): Promise<void> {
@ -580,8 +580,8 @@ export class CodeApplication extends Disposable {
});
}
// mac: open-file event received on startup
if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
// mac: open-file event received on startup
return this.windowsMainService.open({
context: OpenContext.DOCK,
cli: args,

View file

@ -44,26 +44,140 @@ class ExpectedError extends Error {
readonly isExpected = true;
}
function setupIPC(accessor: ServicesAccessor): Promise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const instantiationService = accessor.get(IInstantiationService);
class CodeMain {
async function windowsAllowSetForegroundWindow(service: LaunchChannelClient): Promise<void> {
if (platform.isWindows) {
const processId = await service.getMainProcessId();
main(): void {
logService.trace('Sending some foreground love to the running instance:', processId);
// Set the error handler early enough so that we are not getting the
// default electron error dialog popping up
setUnexpectedErrorHandler(err => console.error(err));
try {
(await import('windows-foreground-love')).allowSetForegroundWindow(processId);
} catch (error) {
logService.error(error);
// Parse arguments
let args: ParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
args = validatePaths(args);
} catch (err) {
console.error(err.message);
app.exit(1);
return;
}
// If we are started with --wait create a random temporary file
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
if (args.wait && !args.waitMarkerFilePath) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
}
}
// Launch
this.startup(args);
}
private async startup(args: ParsedArgs): Promise<void> {
// We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
const bufferLogService = new BufferLogService();
const instantiationService = this.createServices(args, bufferLogService);
try {
await instantiationService.invokeFunction(async accessor => {
const environmentService = accessor.get(IEnvironmentService);
const stateService = accessor.get(IStateService);
// Patch `process.env` with the instance's environment
const instanceEnvironment = this.patchEnvironment(environmentService);
// Startup
try {
await this.initServices(environmentService, stateService as StateService);
} catch (error) {
// Show a dialog for errors that can be resolved by the user
this.handleStartupDataDirError(environmentService, error);
throw error;
}
const mainIpcServer = await instantiationService.invokeFunction(accessor => this.doStartup(accessor, true));
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
});
} catch (error) {
instantiationService.invokeFunction(this.quit, error);
}
}
async function setup(retry: boolean): Promise<Server> {
private createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
return new InstantiationService(services, true);
}
private initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<unknown> {
// Ensure paths for environment service exist
const environmentServiceInitialization = Promise.all<void | undefined>([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome,
environmentService.backupHome
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, stateServiceInitialization]);
}
private patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
const instanceEnvironment: typeof process.env = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnvironment);
return instanceEnvironment;
}
private async doStartup(accessor: ServicesAccessor, retry: boolean): Promise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const instantiationService = accessor.get(IInstantiationService);
// Try to setup a server for running. If that succeeds it means
// we are the first instance to startup. Otherwise it is likely
@ -78,7 +192,7 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
if (error.code !== 'EADDRINUSE') {
// Show a dialog for errors that can be resolved by the user
handleStartupDataDirError(environmentService, error);
this.handleStartupDataDirError(environmentService, error);
// Any other runtime error is just printed to the console
throw error;
@ -98,7 +212,7 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
// Handle unexpected connection errors by showing a dialog to the user
if (!retry || platform.isWindows || error.code !== 'ECONNREFUSED') {
if (error.code === 'EPERM') {
showStartupWarningDialog(
this.showStartupWarningDialog(
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
);
@ -117,7 +231,7 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
throw error;
}
return setup(false);
return this.doStartup(accessor, false);
}
// Tests from CLI require to be the only instance currently
@ -135,7 +249,7 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined;
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
startupWarningDialogHandle = setTimeout(() => {
showStartupWarningDialog(
this.showStartupWarningDialog(
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
);
@ -143,12 +257,12 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
}
const channel = client.getChannel('launch');
const service = new LaunchChannelClient(channel);
const launchClient = new LaunchChannelClient(channel);
// Process Info
if (environmentService.args.status) {
return instantiationService.invokeFunction(async accessor => {
const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(service);
const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(launchClient);
console.log(diagnostics);
throw new ExpectedError();
@ -158,7 +272,7 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
// Log uploader
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
return instantiationService.invokeFunction(async accessor => {
await uploadLogs(service, accessor.get(IRequestService), environmentService);
await uploadLogs(launchClient, accessor.get(IRequestService), environmentService);
throw new ExpectedError();
});
@ -167,12 +281,12 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
// Windows: allow to set foreground
if (platform.isWindows) {
await windowsAllowSetForegroundWindow(service);
await this.windowsAllowSetForegroundWindow(launchClient, logService);
}
// Send environment over...
logService.trace('Sending env to running instance...');
await service.start(environmentService.args, process.env as platform.IProcessEnvironment);
await launchClient.start(environmentService.args, process.env as platform.IProcessEnvironment);
// Cleanup
await client.dispose();
@ -209,177 +323,66 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
return server;
}
return setup(true);
}
function showStartupWarningDialog(message: string, detail: string): void {
dialog.showMessageBox({
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
detail,
noLink: true
});
}
function handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void {
if (error.code === 'EACCES' || error.code === 'EPERM') {
showStartupWarningDialog(
localize('startupDataDirError', "Unable to write program user data."),
localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath)
);
}
}
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
let exitCode = 0;
if (reason) {
if ((reason as ExpectedError).isExpected) {
if (reason.message) {
logService.trace(reason.message);
}
} else {
exitCode = 1; // signal error to the outside
if (reason.stack) {
logService.error(reason.stack);
} else {
logService.error(`Startup error: ${reason.toString()}`);
}
private handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void {
if (error.code === 'EACCES' || error.code === 'EPERM') {
this.showStartupWarningDialog(
localize('startupDataDirError', "Unable to write program user data."),
localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath)
);
}
}
lifecycleService.kill(exitCode);
}
function patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
const instanceEnvironment: typeof process.env = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnvironment);
return instanceEnvironment;
}
async function startup(args: ParsedArgs): Promise<void> {
// We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
const bufferLogService = new BufferLogService();
const instantiationService = createServices(args, bufferLogService);
try {
await instantiationService.invokeFunction(async accessor => {
const environmentService = accessor.get(IEnvironmentService);
const stateService = accessor.get(IStateService);
// Patch `process.env` with the instance's environment
const instanceEnvironment = patchEnvironment(environmentService);
// Startup
try {
await initServices(environmentService, stateService as StateService);
} catch (error) {
// Show a dialog for errors that can be resolved by the user
handleStartupDataDirError(environmentService, error);
throw error;
}
const mainIpcServer = await instantiationService.invokeFunction(setupIPC);
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
private showStartupWarningDialog(message: string, detail: string): void {
dialog.showMessageBox({
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
detail,
noLink: true
});
} catch (error) {
instantiationService.invokeFunction(quit, error);
}
}
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
return new InstantiationService(services, true);
}
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<unknown> {
// Ensure paths for environment service exist
const environmentServiceInitialization = Promise.all<void | undefined>([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome,
environmentService.backupHome
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, stateServiceInitialization]);
}
function main(): void {
// Set the error handler early enough so that we are not getting the
// default electron error dialog popping up
setUnexpectedErrorHandler(err => console.error(err));
// Parse arguments
let args: ParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
args = validatePaths(args);
} catch (err) {
console.error(err.message);
app.exit(1);
return undefined;
}
// If we are started with --wait create a random temporary file
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
if (args.wait && !args.waitMarkerFilePath) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
private async windowsAllowSetForegroundWindow(client: LaunchChannelClient, logService: ILogService): Promise<void> {
if (platform.isWindows) {
const processId = await client.getMainProcessId();
logService.trace('Sending some foreground love to the running instance:', processId);
try {
(await import('windows-foreground-love')).allowSetForegroundWindow(processId);
} catch (error) {
logService.error(error);
}
}
}
startup(args);
private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
let exitCode = 0;
if (reason) {
if ((reason as ExpectedError).isExpected) {
if (reason.message) {
logService.trace(reason.message);
}
} else {
exitCode = 1; // signal error to the outside
if (reason.stack) {
logService.error(reason.stack);
} else {
logService.error(`Startup error: ${reason.toString()}`);
}
}
}
lifecycleService.kill(exitCode);
}
}
main();
// Main Startup
const code = new CodeMain();
code.main();