From 391f7421450567de4abeb4119b98babd7df41efa Mon Sep 17 00:00:00 2001 From: Ramya Rao Date: Thu, 25 May 2017 13:10:39 -0700 Subject: [PATCH] Start crash reporter inside child processes (#27180) * Use service to get crash reporter start options * some refactorings and fixes * Move crashesDirectory to the main payload from extra bag --- src/bootstrap.js | 12 +++ src/typings/electron.d.ts | 7 ++ .../electron-browser/crashReporter.ts | 71 --------------- .../electron-browser/extensionHost.ts | 12 ++- src/vs/workbench/electron-browser/shell.ts | 32 ++----- .../common/crashReporterService.ts | 43 +++++++++ .../electron-browser/crashReporterService.ts | 91 +++++++++++++++++++ 7 files changed, 171 insertions(+), 97 deletions(-) delete mode 100644 src/vs/workbench/electron-browser/crashReporter.ts create mode 100644 src/vs/workbench/services/crashReporter/common/crashReporterService.ts create mode 100644 src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts diff --git a/src/bootstrap.js b/src/bootstrap.js index 022aa50b6a..3f94abadd3 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -126,4 +126,16 @@ if (process.env['VSCODE_PARENT_PID']) { } } +const crashReporterOptionsRaw = process.env['CRASH_REPORTER_START_OPTIONS']; +if (typeof crashReporterOptionsRaw === 'string') { + try { + const crashReporterOptions = JSON.parse(crashReporterOptionsRaw); + if (crashReporterOptions) { + process.crashReporter.start(crashReporterOptions); + } + } catch (error) { + console.error(error); + } +} + require('./bootstrap-amd').bootstrap(process.env['AMD_ENTRYPOINT']); \ No newline at end of file diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 5a1f5ea343..05aab3a816 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -2066,6 +2066,13 @@ declare namespace Electron { * Only string properties are sent correctly, nested objects are not supported. */ extra?: { [prop: string]: string }; + + /** + * Path to a folder where the crashes will be temporarily stored by the electron crash reporter + * Applies only to child processes that need crash reporting. + * Electron figures out the crashesDirectory on its own for Main and Renderer process + */ + crashesDirectory?: string; } interface CrashReport { diff --git a/src/vs/workbench/electron-browser/crashReporter.ts b/src/vs/workbench/electron-browser/crashReporter.ts deleted file mode 100644 index 119229bc2d..0000000000 --- a/src/vs/workbench/electron-browser/crashReporter.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import nls = require('vs/nls'); -import { onUnexpectedError } from 'vs/base/common/errors'; -import { assign, clone } from 'vs/base/common/objects'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Registry } from 'vs/platform/platform'; -import { crashReporter } from 'electron'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; - -const TELEMETRY_SECTION_ID = 'telemetry'; - -interface ICrashReporterConfig { - enableCrashReporter: boolean; -} - -const configurationRegistry = Registry.as(Extensions.Configuration); -configurationRegistry.registerConfiguration({ - 'id': TELEMETRY_SECTION_ID, - 'order': 110, - title: nls.localize('telemetryConfigurationTitle', "Telemetry"), - 'type': 'object', - 'properties': { - 'telemetry.enableCrashReporter': { - 'type': 'boolean', - 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to Microsoft.\nThis option requires restart to take effect."), - 'default': true - } - } -}); - -export class CrashReporter { - - constructor( - configuration: Electron.CrashReporterStartOptions, - @ITelemetryService telemetryService: ITelemetryService, - @IWindowsService windowsService: IWindowsService, - @IConfigurationService configurationService: IConfigurationService - ) { - const config = configurationService.getConfiguration(TELEMETRY_SECTION_ID); - - if (!config.enableCrashReporter) { - return; - } - - telemetryService.getTelemetryInfo() - .then(info => ({ - vscode_sessionId: info.sessionId, - vscode_version: pkg.version, - vscode_commit: product.commit, - vscode_machineId: info.machineId - })) - .then(extra => assign(configuration, { extra })) - .then(configuration => { - // start crash reporter right here - crashReporter.start(clone(configuration)); - - // TODO: start crash reporter in the main process - return windowsService.startCrashReporter(configuration); - }) - .done(null, onUnexpectedError); - } -} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/extensionHost.ts b/src/vs/workbench/electron-browser/extensionHost.ts index e8f82622b4..2431281d98 100644 --- a/src/vs/workbench/electron-browser/extensionHost.ts +++ b/src/vs/workbench/electron-browser/extensionHost.ts @@ -32,6 +32,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { IInitData } from 'vs/workbench/api/node/extHost.protocol'; import { MainProcessExtensionService } from 'vs/workbench/api/electron-browser/mainThreadExtensionService'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { ICrashReporterService } from 'vs/workbench/services/crashReporter/common/crashReporterService'; export const EXTENSION_LOG_BROADCAST_CHANNEL = 'vscode:extensionLog'; export const EXTENSION_ATTACH_BROADCAST_CHANNEL = 'vscode:extensionAttach'; @@ -92,7 +93,9 @@ export class ExtensionHostProcessWorker { @IInstantiationService private instantiationService: IInstantiationService, @IEnvironmentService private environmentService: IEnvironmentService, @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService, - @ITelemetryService private telemetryService: ITelemetryService + @ITelemetryService private telemetryService: ITelemetryService, + @ICrashReporterService private crashReporterService: ICrashReporterService + ) { // handle extension host lifecycle a bit special when we know we are developing an extension that runs inside this.isExtensionDevelopmentHost = environmentService.isExtensionDevelopment; @@ -111,7 +114,7 @@ export class ExtensionHostProcessWorker { const [server, hook] = <[Server, string]>data[0]; const port = data[1]; - let opts = { + const opts = { env: objects.mixin(objects.clone(process.env), { AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess', PIPE_LOGGING: 'true', @@ -130,6 +133,11 @@ export class ExtensionHostProcessWorker { : undefined }; + const crashReporterOptions = this.crashReporterService.getChildProcessStartOptions('extensionHost'); + if (crashReporterOptions) { + opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions); + } + // Run Extension Host as fork of current process this.extensionHostProcess = fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=extensionHost'], opts); diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index da3ce11662..66e974a00c 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -73,7 +73,8 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { WorkbenchModeServiceImpl } from 'vs/workbench/services/mode/common/workbenchModeService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { CrashReporter } from 'vs/workbench/electron-browser/crashReporter'; +import { ICrashReporterService, NullCrashReporterService } from 'vs/workbench/services/crashReporter/common/crashReporterService'; +import { CrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService'; import { NodeCachedDataManager } from 'vs/workbench/electron-browser/nodeCachedDataManager'; import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; @@ -168,29 +169,6 @@ export class WorkbenchShell { // Instantiation service with services const [instantiationService, serviceCollection] = this.initServiceCollection(parent.getHTMLElement()); - //crash reporting - if (product.crashReporter && product.hockeyApp) { - let submitURL: string; - - if (platform.isWindows) { - submitURL = product.hockeyApp[`win32-${process.arch}`]; - } else if (platform.isMacintosh) { - submitURL = product.hockeyApp.darwin; - } else if (platform.isLinux) { - submitURL = product.hockeyApp[`linux-${process.arch}`]; - } - - if (submitURL) { - const opts: Electron.CrashReporterStartOptions = { - companyName: product.crashReporter.companyName, - productName: product.crashReporter.productName, - submitURL - }; - - instantiationService.createInstance(CrashReporter, opts); - } - } - // Workbench this.workbench = instantiationService.createInstance(Workbench, parent.getHTMLElement(), workbenchContainer.getHTMLElement(), this.options, serviceCollection); this.workbench.startup({ @@ -333,6 +311,12 @@ export class WorkbenchShell { serviceCollection.set(ITelemetryService, this.telemetryService); disposables.add(configurationTelemetry(this.telemetryService, this.configurationService)); + let crashReporterService = NullCrashReporterService; + if (product.crashReporter && product.hockeyApp) { + crashReporterService = instantiationService.createInstance(CrashReporterService); + } + serviceCollection.set(ICrashReporterService, crashReporterService); + this.messageService = instantiationService.createInstance(MessageService, container); serviceCollection.set(IMessageService, this.messageService); serviceCollection.set(IChoiceService, this.messageService); diff --git a/src/vs/workbench/services/crashReporter/common/crashReporterService.ts b/src/vs/workbench/services/crashReporter/common/crashReporterService.ts new file mode 100644 index 0000000000..4ce3933901 --- /dev/null +++ b/src/vs/workbench/services/crashReporter/common/crashReporterService.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import nls = require('vs/nls'); +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/platform'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ICrashReporterService = createDecorator('crashReporterService'); + +export const TELEMETRY_SECTION_ID = 'telemetry'; + +export interface ICrashReporterConfig { + enableCrashReporter: boolean; +} + +const configurationRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration({ + 'id': TELEMETRY_SECTION_ID, + 'order': 110, + title: nls.localize('telemetryConfigurationTitle', "Telemetry"), + 'type': 'object', + 'properties': { + 'telemetry.enableCrashReporter': { + 'type': 'boolean', + 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to Microsoft.\nThis option requires restart to take effect."), + 'default': true + } + } +}); + +export interface ICrashReporterService { + _serviceBrand: any; + getChildProcessStartOptions(processName: string): Electron.CrashReporterStartOptions; +} + +export const NullCrashReporterService: ICrashReporterService = { + _serviceBrand: undefined, + getChildProcessStartOptions(processName: string) { return undefined; } +}; \ No newline at end of file diff --git a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts new file mode 100644 index 0000000000..58cca417c6 --- /dev/null +++ b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { onUnexpectedError } from 'vs/base/common/errors'; +import { assign, clone } from 'vs/base/common/objects'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { crashReporter } from 'electron'; +import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/node/package'; +import * as os from 'os'; +import { ICrashReporterService, TELEMETRY_SECTION_ID, ICrashReporterConfig } from "vs/workbench/services/crashReporter/common/crashReporterService"; +import { isWindows, isMacintosh, isLinux } from "vs/base/common/platform"; + +export class CrashReporterService implements ICrashReporterService { + + public _serviceBrand: any; + + private options: Electron.CrashReporterStartOptions; + + constructor( + @ITelemetryService private telemetryService: ITelemetryService, + @IWindowsService private windowsService: IWindowsService, + @IConfigurationService configurationService: IConfigurationService + ) { + const config = configurationService.getConfiguration(TELEMETRY_SECTION_ID); + if (config.enableCrashReporter) { + this.startCrashReporter(); + } + } + + private startCrashReporter(): void { + + // base options + this.options = { + companyName: product.crashReporter.companyName, + productName: product.crashReporter.productName, + submitURL: this.getSubmitURL() + }; + + // mixin telemetry info and product info + this.telemetryService.getTelemetryInfo() + .then(info => { + assign(this.options, { + extra: { + vscode_sessionId: info.sessionId, + vscode_version: pkg.version, + vscode_commit: product.commit, + vscode_machineId: info.machineId + } + }); + + // start crash reporter right here + crashReporter.start(clone(this.options)); + + // start crash reporter in the main process + return this.windowsService.startCrashReporter(this.options); + }) + .done(null, onUnexpectedError); + } + + private getSubmitURL(): string { + let submitURL: string; + if (isWindows) { + submitURL = product.hockeyApp[`win32-${process.arch}`]; + } else if (isMacintosh) { + submitURL = product.hockeyApp.darwin; + } else if (isLinux) { + submitURL = product.hockeyApp[`linux-${process.arch}`]; + } + + return submitURL; + } + + public getChildProcessStartOptions(name: string): Electron.CrashReporterStartOptions { + + // Experimental attempt on Mac only for now + if (isMacintosh) { + const childProcessOptions = clone(this.options); + childProcessOptions.extra.processName = name; + childProcessOptions.crashesDirectory = os.tmpdir(); + return childProcessOptions; + } + + return void 0; + } +} \ No newline at end of file