diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4d17030027a..d6a819dee64 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -37,7 +37,7 @@ import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; @@ -465,7 +465,7 @@ export class CodeApplication extends Disposable { const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer, fileProtocolHandler)); // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentMainService.args.trace) { @@ -926,11 +926,28 @@ export class CodeApplication extends Disposable { return { fileUri: URI.file(path) }; } - private async afterWindowOpen(accessor: ServicesAccessor): Promise { + private async afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): Promise { // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; + // Observe shared process for errors + const telemetryService = accessor.get(ITelemetryService); + this._register(sharedProcess.onDidError(e => { + + // Logging + onUnexpectedError(new Error(e.message)); + + // Telemetry + type SharedProcessErrorClassification = { + type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; + }; + type SharedProcessErrorEvent = { + type: WindowError; + }; + telemetryService.publicLog2('sharedprocesserror', { type: e.type }); + })); + // Windows: install mutex const win32MutexName = product.win32MutexName; if (isWindows && win32MutexName) { diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 889a2c3b85f..8c40cb0f9c3 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron'; +import { BrowserWindow, ipcMain, Event as ElectronEvent, MessagePortMain } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; @@ -15,14 +15,18 @@ import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedP import { Disposable } from 'vs/base/common/lifecycle'; import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { assertIsDefined } from 'vs/base/common/types'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import { WindowError } from 'vs/platform/windows/electron-main/windows'; export class SharedProcess extends Disposable implements ISharedProcess { private readonly whenSpawnedBarrier = new Barrier(); private window: BrowserWindow | undefined = undefined; - private windowCloseListener: ((event: Event) => void) | undefined = undefined; + private windowCloseListener: ((event: ElectronEvent) => void) | undefined = undefined; + + private readonly _onDidError = this._register(new Emitter<{ type: WindowError, message: string }>()); + readonly onDidError = Event.buffer(this._onDidError.event); // buffer until we have a listener! constructor( private readonly machineId: string, @@ -187,7 +191,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } // Prevent the window from closing - this.windowCloseListener = (e: Event) => { + this.windowCloseListener = (e: ElectronEvent) => { this.logService.trace('SharedProcess#close prevented'); // We never allow to close the shared process unless we get explicitly disposed() @@ -204,9 +208,9 @@ export class SharedProcess extends Disposable implements ISharedProcess { // Crashes & Unrsponsive & Failed to load // We use `onUnexpectedError` explicitly because the error handler // will send the error to the active window to log in devtools too - this.window.webContents.on('render-process-gone', (event, details) => onUnexpectedError(new Error(`SharedProcess: crashed (detail: ${details?.reason})`))); - this.window.on('unresponsive', () => onUnexpectedError(new Error('SharedProcess: detected unresponsive window'))); - this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => onUnexpectedError(new Error(`SharedProcess: failed to load window: ${errorDescription}`))); + this.window.webContents.on('render-process-gone', (event, details) => this._onDidError.fire({ type: WindowError.CRASHED, message: `SharedProcess: crashed (detail: ${details?.reason})` })); + this.window.on('unresponsive', () => this._onDidError.fire({ type: WindowError.UNRESPONSIVE, message: 'SharedProcess: detected unresponsive window' })); + this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this._onDidError.fire({ type: WindowError.LOAD, message: `SharedProcess: failed to load window: ${errorDescription}` })); } spawn(userEnv: NodeJS.ProcessEnv): void { diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index c386a584857..a68581665d7 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { getMarks, mark } from 'vs/base/common/performance'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, RenderProcessGoneDetails } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,7 +19,7 @@ import product from 'vs/platform/product/common/product'; import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { defaultWindowState, ICodeWindow, ILoadEvent, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { defaultWindowState, ICodeWindow, ILoadEvent, IWindowState, WindowError, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -48,11 +48,6 @@ interface ITouchBarSegment extends SegmentedControlSegment { id: string; } -const enum WindowError { - UNRESPONSIVE = 1, - CRASHED = 2 -} - const enum ReadyState { /** @@ -403,9 +398,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { private registerListeners(): void { // Crashes & Unrsponsive & Failed to load - this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details)); this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); - this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('Main: failed to load workbench window, ', errorDescription)); + this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details.reason)); + this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.onWindowError(WindowError.LOAD, errorDescription)); // Window close this._win.on('closed', () => { @@ -550,9 +545,21 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private async onWindowError(error: WindowError.UNRESPONSIVE): Promise; - private async onWindowError(error: WindowError.CRASHED, details: RenderProcessGoneDetails): Promise; - private async onWindowError(error: WindowError, details?: RenderProcessGoneDetails): Promise { - this.logService.error(error === WindowError.CRASHED ? `Main: renderer process crashed (detail: ${details?.reason})` : 'Main: detected unresponsive'); + private async onWindowError(error: WindowError.CRASHED, details: string): Promise; + private async onWindowError(error: WindowError.LOAD, details: string): Promise; + private async onWindowError(type: WindowError, details?: string): Promise { + + switch (type) { + case WindowError.CRASHED: + this.logService.error(`CodeWindow: renderer process crashed (detail: ${details})`); + break; + case WindowError.UNRESPONSIVE: + this.logService.error('CodeWindow: detected unresponsive'); + break; + case WindowError.LOAD: + this.logService.error(`CodeWindow: failed to load workbench window: ${details}`); + break; + } // If we run extension tests from CLI, showing a dialog is not // very helpful in this case. Rather, we bring down the test run @@ -569,10 +576,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { type WindowErrorEvent = { type: WindowError; }; - this.telemetryService.publicLog2('windowerror', { type: error }); + this.telemetryService.publicLog2('windowerror', { type }); // Unresponsive - if (error === WindowError.UNRESPONSIVE) { + if (type === WindowError.UNRESPONSIVE) { if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { // TODO@bpasero Workaround for https://github.com/microsoft/vscode/issues/56994 // In certain cases the window can report unresponsiveness because a breakpoint was hit @@ -606,12 +613,12 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Crashed - else { + else if (type === WindowError.CRASHED) { let message: string; - if (details && details.reason !== 'crashed') { - message = localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details?.reason); + if (details && details !== 'crashed') { + message = localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details); } else { - message = localize('appCrashed', "The window has crashed", details?.reason); + message = localize('appCrashed', "The window has crashed", details); } const result = await this.dialogMainService.showMessageBox({ diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 5a1446b4c4f..96f92d28946 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -125,6 +125,24 @@ export interface ICodeWindow extends IDisposable { serializeWindowState(): IWindowState; } +export const enum WindowError { + + /** + * Maps to the `unresponsive` event on a `BrowserWindow`. + */ + UNRESPONSIVE = 1, + + /** + * Maps to the `render-proces-gone` event on a `WebContents`. + */ + CRASHED = 2, + + /** + * Maps to the `did-fail-load` event on a `WebContents`. + */ + LOAD = 3 +} + export const IWindowsMainService = createDecorator('windowsMainService'); export interface IWindowsCountChangedEvent {