提交 252a673d 编写于 作者: A Alex Dima

Fixes #58612: Detect when the extension host is unresponsive

上级 c0bbadd9
......@@ -15,12 +15,13 @@ import { append, $, addDisposableListener } from 'vs/base/browser/dom';
import { IStatusbarRegistry, StatusbarItemDescriptor, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionHostProfileService, ProfileSessionState, RuntimeExtensionsInput } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor';
import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { randomPort } from 'vs/base/node/ports';
import product from 'vs/platform/node/product';
import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput';
export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService {
......
......@@ -38,9 +38,10 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/parts/extensions/browser/extensionsQuickOpen';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { RuntimeExtensionsEditor, RuntimeExtensionsInput, ShowRuntimeExtensionsAction, IExtensionHostProfileService } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor';
import { RuntimeExtensionsEditor, ShowRuntimeExtensionsAction, IExtensionHostProfileService } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor';
import { EditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { ExtensionHostProfileService } from 'vs/workbench/parts/extensions/electron-browser/extensionProfileService';
import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
......
......@@ -9,8 +9,6 @@ import 'vs/css!./media/runtimeExtensionsEditor';
import * as nls from 'vs/nls';
import * as os from 'os';
import product from 'vs/platform/node/product';
import { URI } from 'vs/base/common/uri';
import { EditorInput } from 'vs/workbench/common/editor';
import pkg from 'vs/platform/node/package';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action, IAction } from 'vs/base/common/actions';
......@@ -38,6 +36,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { Event } from 'vs/base/common/event';
import { DisableForWorkspaceAction, DisableGloballyAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput';
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
......@@ -430,45 +429,6 @@ export class RuntimeExtensionsEditor extends BaseEditor {
}
}
export class RuntimeExtensionsInput extends EditorInput {
static readonly ID = 'workbench.runtimeExtensions.input';
constructor() {
super();
}
getTypeId(): string {
return RuntimeExtensionsInput.ID;
}
getName(): string {
return nls.localize('extensionsInputName', "Running Extensions");
}
matches(other: any): boolean {
if (!(other instanceof RuntimeExtensionsInput)) {
return false;
}
return true;
}
resolve(): TPromise<any> {
return TPromise.as(null);
}
supportsSplitEditor(): boolean {
return false;
}
getResource(): URI {
return URI.from({
scheme: 'runtime-extensions',
path: 'default'
});
}
}
export class ShowRuntimeExtensionsAction extends Action {
static readonly ID = 'workbench.action.showRuntimeExtensions';
static LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions");
......
......@@ -490,9 +490,9 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
// (graceful termination)
protocol.send(createMessageOfType(MessageType.Terminate));
// Give the extension host 60s, after which we will
// Give the extension host 10s, after which we will
// try to kill the process and release any resources
setTimeout(() => this._cleanResources(), 60 * 1000);
setTimeout(() => this._cleanResources(), 10 * 1000);
}, (err) => {
......
......@@ -39,12 +39,14 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import product from 'vs/platform/node/product';
import * as strings from 'vs/base/common/strings';
import { RPCProtocol, IRPCProtocolLogger, RequestInitiator } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { RPCProtocol, IRPCProtocolLogger, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { Schemas } from 'vs/base/common/network';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { isEqualOrParent } from 'vs/base/common/resources';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
......@@ -115,6 +117,9 @@ export class ExtensionHostProcessManager extends Disposable {
public readonly onDidCrash: Event<[number, string]>;
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
*/
......@@ -187,6 +192,7 @@ export class ExtensionHostProcessManager extends Disposable {
}
this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger);
this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
const extHostContext: IExtHostContext = {
getProxy: <T>(identifier: ProxyIdentifier<T>): T => this._extensionHostProcessRPCProtocol.getProxy(identifier),
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._extensionHostProcessRPCProtocol.set(identifier, instance),
......@@ -262,6 +268,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
private readonly _onDidChangeExtensionsStatus: Emitter<string[]> = this._register(new Emitter<string[]>());
public readonly onDidChangeExtensionsStatus: Event<string[]> = this._onDidChangeExtensionsStatus.event;
private _unresponsiveNotificationHandle: INotificationHandle;
// --- Members used per extension host process
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
......@@ -288,6 +296,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._onDidRegisterExtensions = new Emitter<void>();
this._unresponsiveNotificationHandle = null;
this._extensionHostProcessManagers = [];
this._extensionHostProcessActivationTimes = Object.create(null);
this._extensionHostExtensionRuntimeErrors = Object.create(null);
......@@ -375,6 +385,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, this.getExtensions(), this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => this._onResponsiveStateChanged(responsiveState));
this._extensionHostProcessManagers.push(extHostProcessManager);
}
......@@ -399,6 +410,49 @@ export class ExtensionService extends Disposable implements IExtensionService {
);
}
private _onResponsiveStateChanged(state: ResponsiveState): void {
if (this._unresponsiveNotificationHandle) {
this._unresponsiveNotificationHandle.close();
this._unresponsiveNotificationHandle = null;
}
const showRunningExtensions = {
keepOpen: true,
label: nls.localize('extensionHostProcess.unresponsive.inspect', "Show running extensions"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const editorService = accessor.get(IEditorService);
editorService.openEditor(this._instantiationService.createInstance(RuntimeExtensionsInput), { revealIfOpened: true });
// keepOpen does not appear to work
setTimeout(() => {
this._onResponsiveStateChanged(state);
}, 100);
});
}
};
const restartExtensionHost = {
label: nls.localize('extensionHostProcess.unresponsive.restart', "Restart Extension Host"),
run: () => {
this.restartExtensionHost();
}
};
if (state === ResponsiveState.Unresponsive) {
this._unresponsiveNotificationHandle = this._notificationService.prompt(
Severity.Warning,
nls.localize('extensionHostProcess.unresponsive', "Extension Host is unresponsive."),
[showRunningExtensions, restartExtensionHost]
);
} else {
this._unresponsiveNotificationHandle = this._notificationService.prompt(
Severity.Info,
nls.localize('extensionHostProcess.responsive', "Extension Host is now responsive."),
[showRunningExtensions]
);
}
}
// ---- begin IExtensionService
public activateByEvent(activationEvent: string): TPromise<void> {
......
/*---------------------------------------------------------------------------------------------
* 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 * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { EditorInput } from 'vs/workbench/common/editor';
import { TPromise } from 'vs/base/common/winjs.base';
export class RuntimeExtensionsInput extends EditorInput {
static readonly ID = 'workbench.runtimeExtensions.input';
constructor() {
super();
}
getTypeId(): string {
return RuntimeExtensionsInput.ID;
}
getName(): string {
return nls.localize('extensionsInputName', "Running Extensions");
}
matches(other: any): boolean {
if (!(other instanceof RuntimeExtensionsInput)) {
return false;
}
return true;
}
resolve(): TPromise<any> {
return TPromise.as(null);
}
supportsSplitEditor(): boolean {
return false;
}
getResource(): URI {
return URI.from({
scheme: 'runtime-extensions',
path: 'default'
});
}
}
......@@ -14,6 +14,9 @@ import { URI } from 'vs/base/common/uri';
import { MarshalledObject } from 'vs/base/common/marshalling';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
declare var Proxy: any; // TODO@TypeScript
......@@ -91,6 +94,11 @@ export const enum RequestInitiator {
OtherSide = 1
}
export const enum ResponsiveState {
Responsive = 0,
Unresponsive = 1
}
export interface IRPCProtocolLogger {
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void;
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void;
......@@ -98,7 +106,12 @@ export interface IRPCProtocolLogger {
const noop = () => { };
export class RPCProtocol implements IRPCProtocol {
export class RPCProtocol extends Disposable implements IRPCProtocol {
private static UNRESPONSIVE_TIME = 10 * 1000; // 10s
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
private readonly _protocol: IMessagePassingProtocol;
private readonly _logger: IRPCProtocolLogger;
......@@ -109,8 +122,13 @@ export class RPCProtocol implements IRPCProtocol {
private _lastMessageId: number;
private readonly _cancelInvokedHandlers: { [req: string]: () => void; };
private readonly _pendingRPCReplies: { [msgId: string]: LazyPromise; };
private _responsiveState: ResponsiveState;
private _pendingRPCRepliesCount: number;
private _unresponsiveTime: number;
private _asyncCheckUresponsive: RunOnceScheduler;
constructor(protocol: IMessagePassingProtocol, logger: IRPCProtocolLogger = null, transformer: IURITransformer = null) {
super();
this._protocol = protocol;
this._logger = logger;
this._uriTransformer = transformer;
......@@ -124,6 +142,10 @@ export class RPCProtocol implements IRPCProtocol {
this._lastMessageId = 0;
this._cancelInvokedHandlers = Object.create(null);
this._pendingRPCReplies = {};
this._responsiveState = ResponsiveState.Responsive;
this._pendingRPCRepliesCount = 0;
this._unresponsiveTime = 0;
this._asyncCheckUresponsive = this._register(new RunOnceScheduler(() => this._checkUnresponsive(), 1000));
this._protocol.onMessage((msg) => this._receiveOneMessage(msg));
}
......@@ -137,6 +159,60 @@ export class RPCProtocol implements IRPCProtocol {
});
}
private _onWillSendRequest(): void {
if (this._pendingRPCRepliesCount === 0) {
// Since this is the first request we are sending in a while,
// mark this moment as the start for the countdown to unresponsive time
this._unresponsiveTime = Date.now() + RPCProtocol.UNRESPONSIVE_TIME;
}
this._pendingRPCRepliesCount++;
if (!this._asyncCheckUresponsive.isScheduled()) {
this._asyncCheckUresponsive.schedule();
}
}
private _onWillReceiveReply(): void {
// The next possible unresponsive time is now + delta.
this._unresponsiveTime = Date.now() + RPCProtocol.UNRESPONSIVE_TIME;
this._pendingRPCRepliesCount--;
if (this._pendingRPCRepliesCount === 0) {
// No more need to check for unresponsive
this._asyncCheckUresponsive.cancel();
}
// The ext host is responsive!
this._setResponsiveState(ResponsiveState.Responsive);
}
private _checkUnresponsive(): void {
if (this._pendingRPCRepliesCount === 0) {
// Not waiting for anything => cannot say if it is responsive or not
return;
}
if (Date.now() > this._unresponsiveTime) {
// Unresponsive!!
this._setResponsiveState(ResponsiveState.Unresponsive);
} else {
// Not (yet) unresponsive, be sure to check again soon
if (this._pendingRPCRepliesCount > 0) {
this._asyncCheckUresponsive.schedule();
}
}
}
private _setResponsiveState(newResponsiveState: ResponsiveState): void {
if (this._responsiveState === newResponsiveState) {
// no change
return;
}
this._responsiveState = newResponsiveState;
this._onDidChangeResponsiveState.fire(this._responsiveState);
}
public get responsiveState(): ResponsiveState {
return this._responsiveState;
}
public transformIncomingURIs<T>(obj: T): T {
if (!this._uriTransformer) {
return obj;
......@@ -307,6 +383,7 @@ export class RPCProtocol implements IRPCProtocol {
const pendingReply = this._pendingRPCReplies[callId];
delete this._pendingRPCReplies[callId];
this._onWillReceiveReply();
pendingReply.resolveOk(value);
}
......@@ -323,6 +400,7 @@ export class RPCProtocol implements IRPCProtocol {
const pendingReply = this._pendingRPCReplies[callId];
delete this._pendingRPCReplies[callId];
this._onWillReceiveReply();
let err: Error = null;
if (value && value.$isError) {
......@@ -383,6 +461,7 @@ export class RPCProtocol implements IRPCProtocol {
}
this._pendingRPCReplies[callId] = result;
this._onWillSendRequest();
if (this._uriTransformer) {
args = transformOutgoingURIs(args, this._uriTransformer);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册