Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start crash reporter inside child processes #27180

Merged
merged 3 commits into from
May 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
7 changes: 7 additions & 0 deletions src/typings/electron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
71 changes: 0 additions & 71 deletions src/vs/workbench/electron-browser/crashReporter.ts

This file was deleted.

12 changes: 10 additions & 2 deletions src/vs/workbench/electron-browser/extensionHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -111,7 +114,7 @@ export class ExtensionHostProcessWorker {
const [server, hook] = <[Server, string]>data[0];
const port = <number>data[1];

let opts = {
const opts = {
env: objects.mixin(objects.clone(process.env), {
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
PIPE_LOGGING: 'true',
Expand All @@ -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);

Expand Down
32 changes: 8 additions & 24 deletions src/vs/workbench/electron-browser/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -169,29 +170,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({
Expand Down Expand Up @@ -350,6 +328,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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ICrashReporterService>('crashReporterService');

export const TELEMETRY_SECTION_ID = 'telemetry';

export interface ICrashReporterConfig {
enableCrashReporter: boolean;
}

const configurationRegistry = <IConfigurationRegistry>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; }
};
Original file line number Diff line number Diff line change
@@ -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<ICrashReporterConfig>(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;
}
}