Skip to content

Commit 29a7a86

Browse files
ramya-rao-adbaeumer
authored andcommitted
Start crash reporter inside child processes (microsoft#27180)
* Use service to get crash reporter start options * some refactorings and fixes * Move crashesDirectory to the main payload from extra bag
1 parent f962f52 commit 29a7a86

7 files changed

Lines changed: 171 additions & 97 deletions

File tree

src/bootstrap.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,16 @@ if (process.env['VSCODE_PARENT_PID']) {
126126
}
127127
}
128128

129+
const crashReporterOptionsRaw = process.env['CRASH_REPORTER_START_OPTIONS'];
130+
if (typeof crashReporterOptionsRaw === 'string') {
131+
try {
132+
const crashReporterOptions = JSON.parse(crashReporterOptionsRaw);
133+
if (crashReporterOptions) {
134+
process.crashReporter.start(crashReporterOptions);
135+
}
136+
} catch (error) {
137+
console.error(error);
138+
}
139+
}
140+
129141
require('./bootstrap-amd').bootstrap(process.env['AMD_ENTRYPOINT']);

src/typings/electron.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,13 @@ declare namespace Electron {
20662066
* Only string properties are sent correctly, nested objects are not supported.
20672067
*/
20682068
extra?: { [prop: string]: string };
2069+
2070+
/**
2071+
* Path to a folder where the crashes will be temporarily stored by the electron crash reporter
2072+
* Applies only to child processes that need crash reporting.
2073+
* Electron figures out the crashesDirectory on its own for Main and Renderer process
2074+
*/
2075+
crashesDirectory?: string;
20692076
}
20702077

20712078
interface CrashReport {

src/vs/workbench/electron-browser/crashReporter.ts

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

src/vs/workbench/electron-browser/extensionHost.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import Event, { Emitter } from 'vs/base/common/event';
3232
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
3333
import { MainProcessExtensionService } from 'vs/workbench/api/electron-browser/mainThreadExtensionService';
3434
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
35+
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/common/crashReporterService';
3536

3637
export const EXTENSION_LOG_BROADCAST_CHANNEL = 'vscode:extensionLog';
3738
export const EXTENSION_ATTACH_BROADCAST_CHANNEL = 'vscode:extensionAttach';
@@ -92,7 +93,9 @@ export class ExtensionHostProcessWorker {
9293
@IInstantiationService private instantiationService: IInstantiationService,
9394
@IEnvironmentService private environmentService: IEnvironmentService,
9495
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
95-
@ITelemetryService private telemetryService: ITelemetryService
96+
@ITelemetryService private telemetryService: ITelemetryService,
97+
@ICrashReporterService private crashReporterService: ICrashReporterService
98+
9699
) {
97100
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
98101
this.isExtensionDevelopmentHost = environmentService.isExtensionDevelopment;
@@ -111,7 +114,7 @@ export class ExtensionHostProcessWorker {
111114
const [server, hook] = <[Server, string]>data[0];
112115
const port = <number>data[1];
113116

114-
let opts = {
117+
const opts = {
115118
env: objects.mixin(objects.clone(process.env), {
116119
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
117120
PIPE_LOGGING: 'true',
@@ -130,6 +133,11 @@ export class ExtensionHostProcessWorker {
130133
: undefined
131134
};
132135

136+
const crashReporterOptions = this.crashReporterService.getChildProcessStartOptions('extensionHost');
137+
if (crashReporterOptions) {
138+
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions);
139+
}
140+
133141
// Run Extension Host as fork of current process
134142
this.extensionHostProcess = fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=extensionHost'], opts);
135143

src/vs/workbench/electron-browser/shell.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions';
7373
import { WorkbenchModeServiceImpl } from 'vs/workbench/services/mode/common/workbenchModeService';
7474
import { IModeService } from 'vs/editor/common/services/modeService';
7575
import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
76-
import { CrashReporter } from 'vs/workbench/electron-browser/crashReporter';
76+
import { ICrashReporterService, NullCrashReporterService } from 'vs/workbench/services/crashReporter/common/crashReporterService';
77+
import { CrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService';
7778
import { NodeCachedDataManager } from 'vs/workbench/electron-browser/nodeCachedDataManager';
7879
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
7980
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
@@ -168,29 +169,6 @@ export class WorkbenchShell {
168169
// Instantiation service with services
169170
const [instantiationService, serviceCollection] = this.initServiceCollection(parent.getHTMLElement());
170171

171-
//crash reporting
172-
if (product.crashReporter && product.hockeyApp) {
173-
let submitURL: string;
174-
175-
if (platform.isWindows) {
176-
submitURL = product.hockeyApp[`win32-${process.arch}`];
177-
} else if (platform.isMacintosh) {
178-
submitURL = product.hockeyApp.darwin;
179-
} else if (platform.isLinux) {
180-
submitURL = product.hockeyApp[`linux-${process.arch}`];
181-
}
182-
183-
if (submitURL) {
184-
const opts: Electron.CrashReporterStartOptions = {
185-
companyName: product.crashReporter.companyName,
186-
productName: product.crashReporter.productName,
187-
submitURL
188-
};
189-
190-
instantiationService.createInstance(CrashReporter, opts);
191-
}
192-
}
193-
194172
// Workbench
195173
this.workbench = instantiationService.createInstance(Workbench, parent.getHTMLElement(), workbenchContainer.getHTMLElement(), this.options, serviceCollection);
196174
this.workbench.startup({
@@ -333,6 +311,12 @@ export class WorkbenchShell {
333311
serviceCollection.set(ITelemetryService, this.telemetryService);
334312
disposables.add(configurationTelemetry(this.telemetryService, this.configurationService));
335313

314+
let crashReporterService = NullCrashReporterService;
315+
if (product.crashReporter && product.hockeyApp) {
316+
crashReporterService = instantiationService.createInstance(CrashReporterService);
317+
}
318+
serviceCollection.set(ICrashReporterService, crashReporterService);
319+
336320
this.messageService = instantiationService.createInstance(MessageService, container);
337321
serviceCollection.set(IMessageService, this.messageService);
338322
serviceCollection.set(IChoiceService, this.messageService);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
import nls = require('vs/nls');
8+
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
9+
import { Registry } from 'vs/platform/platform';
10+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
11+
12+
export const ICrashReporterService = createDecorator<ICrashReporterService>('crashReporterService');
13+
14+
export const TELEMETRY_SECTION_ID = 'telemetry';
15+
16+
export interface ICrashReporterConfig {
17+
enableCrashReporter: boolean;
18+
}
19+
20+
const configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
21+
configurationRegistry.registerConfiguration({
22+
'id': TELEMETRY_SECTION_ID,
23+
'order': 110,
24+
title: nls.localize('telemetryConfigurationTitle', "Telemetry"),
25+
'type': 'object',
26+
'properties': {
27+
'telemetry.enableCrashReporter': {
28+
'type': 'boolean',
29+
'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to Microsoft.\nThis option requires restart to take effect."),
30+
'default': true
31+
}
32+
}
33+
});
34+
35+
export interface ICrashReporterService {
36+
_serviceBrand: any;
37+
getChildProcessStartOptions(processName: string): Electron.CrashReporterStartOptions;
38+
}
39+
40+
export const NullCrashReporterService: ICrashReporterService = {
41+
_serviceBrand: undefined,
42+
getChildProcessStartOptions(processName: string) { return undefined; }
43+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
import { onUnexpectedError } from 'vs/base/common/errors';
8+
import { assign, clone } from 'vs/base/common/objects';
9+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
10+
import { IWindowsService } from 'vs/platform/windows/common/windows';
11+
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
12+
import { crashReporter } from 'electron';
13+
import product from 'vs/platform/node/product';
14+
import pkg from 'vs/platform/node/package';
15+
import * as os from 'os';
16+
import { ICrashReporterService, TELEMETRY_SECTION_ID, ICrashReporterConfig } from "vs/workbench/services/crashReporter/common/crashReporterService";
17+
import { isWindows, isMacintosh, isLinux } from "vs/base/common/platform";
18+
19+
export class CrashReporterService implements ICrashReporterService {
20+
21+
public _serviceBrand: any;
22+
23+
private options: Electron.CrashReporterStartOptions;
24+
25+
constructor(
26+
@ITelemetryService private telemetryService: ITelemetryService,
27+
@IWindowsService private windowsService: IWindowsService,
28+
@IConfigurationService configurationService: IConfigurationService
29+
) {
30+
const config = configurationService.getConfiguration<ICrashReporterConfig>(TELEMETRY_SECTION_ID);
31+
if (config.enableCrashReporter) {
32+
this.startCrashReporter();
33+
}
34+
}
35+
36+
private startCrashReporter(): void {
37+
38+
// base options
39+
this.options = {
40+
companyName: product.crashReporter.companyName,
41+
productName: product.crashReporter.productName,
42+
submitURL: this.getSubmitURL()
43+
};
44+
45+
// mixin telemetry info and product info
46+
this.telemetryService.getTelemetryInfo()
47+
.then(info => {
48+
assign(this.options, {
49+
extra: {
50+
vscode_sessionId: info.sessionId,
51+
vscode_version: pkg.version,
52+
vscode_commit: product.commit,
53+
vscode_machineId: info.machineId
54+
}
55+
});
56+
57+
// start crash reporter right here
58+
crashReporter.start(clone(this.options));
59+
60+
// start crash reporter in the main process
61+
return this.windowsService.startCrashReporter(this.options);
62+
})
63+
.done(null, onUnexpectedError);
64+
}
65+
66+
private getSubmitURL(): string {
67+
let submitURL: string;
68+
if (isWindows) {
69+
submitURL = product.hockeyApp[`win32-${process.arch}`];
70+
} else if (isMacintosh) {
71+
submitURL = product.hockeyApp.darwin;
72+
} else if (isLinux) {
73+
submitURL = product.hockeyApp[`linux-${process.arch}`];
74+
}
75+
76+
return submitURL;
77+
}
78+
79+
public getChildProcessStartOptions(name: string): Electron.CrashReporterStartOptions {
80+
81+
// Experimental attempt on Mac only for now
82+
if (isMacintosh) {
83+
const childProcessOptions = clone(this.options);
84+
childProcessOptions.extra.processName = name;
85+
childProcessOptions.crashesDirectory = os.tmpdir();
86+
return childProcessOptions;
87+
}
88+
89+
return void 0;
90+
}
91+
}

0 commit comments

Comments
 (0)