提交 8e89e343 编写于 作者: A Alex Dima

Add extension host client

上级 8a7c6a0c
......@@ -16,6 +16,12 @@ import { URI } from 'vs/base/common/uri';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/product/node/package';
......@@ -35,6 +41,7 @@ import { Schemas } from 'vs/base/common/network';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
......@@ -64,6 +71,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
private _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
private readonly _extensionHostLogsLocation: URI;
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
......@@ -102,6 +111,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IWindowService private readonly _windowService: IWindowService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IFileService fileService: IFileService
) {
......@@ -112,7 +124,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}));
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`));
this._registry = new ExtensionDescriptionRegistry([]);
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
......@@ -425,6 +439,17 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private _createProvider(remoteAuthority: string): IInitDataProvider {
return {
remoteAuthority: remoteAuthority,
getInitData: () => {
return this._installedExtensionsReady.wait().then(() => {
return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!;
});
}
};
}
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
......@@ -436,52 +461,63 @@ export class ExtensionService extends Disposable implements IExtensionService {
} else {
// restart case
autoStart = true;
extensions = this.getExtensions();
extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file));
}
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(extHostProcessManager);
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority));
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false));
remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(remoteExtHostProcessManager);
}
}
private _onExtensionHostCrashed(code: number, signal: string | null): void {
private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHostProcess();
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
if (showNotification) {
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
}]
);
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
[{
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
}
// ---- begin IExtensionService
......@@ -590,12 +626,112 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
}
private async _resolveAuthorityAgain(): Promise<void> {
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return;
}
const extensionHost = this._extensionHostProcessManagers[0];
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
try {
const resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
} catch (err) {
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
}
}
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(this.createLogger());
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const extensionHost = this._extensionHostProcessManagers[0];
const extensions = await this._extensionScanner.scannedExtensions;
const enabledExtensions = await this._getRuntimeExtensions(extensions);
let localExtensions = await this._extensionScanner.scannedExtensions;
if (remoteAuthority) {
let resolvedAuthority: ResolvedAuthority;
try {
resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
} catch (err) {
console.error(err);
const plusIndex = remoteAuthority.indexOf('+');
const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority;
if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) {
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) });
} else {
console.log(`Not showing a notification for the error`);
}
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
// Proceed with the local extension host
await this._startLocalExtensionHost(extensionHost, localExtensions);
return;
}
// set the resolved authority
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
// monitor for breakage
const connection = this._remoteAgentService.getConnection();
if (connection) {
connection.onDidStateChange(async (e) => {
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return;
}
if (e.type === PersistenConnectionEventType.ConnectionLost) {
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
}
});
connection.onReconnecting(() => this._resolveAuthorityAgain());
}
// fetch the remote environment
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
// revive URIs
remoteEnv.extensions.forEach((extension) => {
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
});
// remove UI extensions from the remote extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService));
// remove non-UI extensions from the local extensions
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService));
// in case of overlap, the remote wins
const isRemoteExtension = new Set<string>();
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
// compute enabled extensions
const enabledExtensions = await this._getRuntimeExtensions((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
// remove disabled extensions
const isEnabled = new Set<string>();
enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
// save for remote extension's init data
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(localExtensions.map(extension => extension.identifier));
this._releaseBarrier();
} else {
await this._startLocalExtensionHost(extensionHost, localExtensions);
}
}
private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise<void> {
const enabledExtensions = await this._getRuntimeExtensions(localExtensions);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer as ipc } from 'electron';
import { Emitter, Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as platform from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { VSBuffer } from 'vs/base/common/buffer';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
export interface IInitDataProvider {
readonly remoteAuthority: string;
getInitData(): Promise<IRemoteAgentEnvironment>;
}
export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter {
private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event;
private _protocol: PersistentProtocol | null;
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevTestFromCli: boolean;
private _terminating: boolean;
constructor(
private readonly _allExtensions: Promise<IExtensionDescription[]>,
private readonly _initDataProvider: IInitDataProvider,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
) {
super();
this._protocol = null;
this._terminating = false;
this._register(this._lifecycleService.onShutdown(reason => this.dispose()));
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
}
public start(): Promise<IMessagePassingProtocol> {
const options: IConnectionOptions = {
isBuilt: this._environmentService.isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host, port };
}
}
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => {
const startParams: IRemoteExtensionHostStartParams = {
language: platform.language,
debugId: this._environmentService.debugExtensionHost.debugId,
break: this._environmentService.debugExtensionHost.break,
port: this._environmentService.debugExtensionHost.port,
};
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
let debugOk = true;
if (extDevLocs && extDevLocs.length > 0) {
// TODO@AW: handles only first path in array
if (extDevLocs[0].scheme === Schemas.file) {
debugOk = false;
}
}
if (!debugOk) {
startParams.break = false;
}
return connectRemoteAgentExtensionHost(options, startParams).then(result => {
let { protocol, debugPort } = result;
const isExtensionDevelopmentDebug = typeof debugPort === 'number';
if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
}
protocol.onClose(() => {
this._onExtHostConnectionLost();
});
protocol.onSocketClose(() => {
if (this._isExtensionDevHost) {
this._onExtHostConnectionLost();
}
});
// 1) wait for the incoming `ready` event and send the initialization data.
// 2) wait for the incoming `initialized` event.
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
let handle = setTimeout(() => {
reject('timeout');
}, 60 * 1000);
const disposable = protocol.onMessage(msg => {
if (isMessageOfType(msg, MessageType.Ready)) {
// 1) Extension Host is ready to receive messages, initialize it
this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => protocol.send(VSBuffer.fromString(JSON.stringify(data))));
return;
}
if (isMessageOfType(msg, MessageType.Initialized)) {
// 2) Extension Host is initialized
clearTimeout(handle);
// stop listening for messages here
disposable.dispose();
// release this promise
this._protocol = protocol;
resolve(protocol);
return;
}
console.error(`received unexpected message during handshake phase from the extension host: `, msg);
});
});
});
});
}
private _onExtHostConnectionLost(): void {
if (this._terminating) {
// Expected termination path (we asked the process to terminate)
return;
}
// Unexpected termination
if (!this._isExtensionDevHost) {
this._onCrashed.fire([0, null]);
}
// Expected development extension termination: When the extension host goes down we also shutdown the window
else if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
}
// When CLI testing make sure to exit with proper exit code
else {
ipc.send('vscode:exit', 0);
}
}
private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IInitData> {
return Promise.all([this._allExtensions, this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]).then(([allExtensions, telemetryInfo, remoteExtensionHostData]) => {
// Collect all identifiers for extension ids which can be considered "resolved"
const resolvedExtensions = allExtensions.filter(extension => !extension.main).map(extension => extension.identifier);
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
version: pkg.version,
parentPid: remoteExtensionHostData.pid,
environment: {
isExtensionDevelopmentDebug,
appRoot: remoteExtensionHostData.appRoot,
appSettingsHome: remoteExtensionHostData.appSettingsHome,
appName: product.nameLong,
appUriScheme: product.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: remoteExtensionHostData.globalStorageHome,
userHome: remoteExtensionHostData.userHome
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
resolvedExtensions: resolvedExtensions,
hostExtensions: hostExtensions,
extensions: remoteExtensionHostData.extensions,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: remoteExtensionHostData.extensionHostLogsPath,
autoStart: true,
remoteAuthority: this._initDataProvider.remoteAuthority,
};
return r;
});
}
getInspectPort(): number | undefined {
return undefined;
}
dispose(): void {
super.dispose();
this._terminating = true;
if (this._protocol) {
// Send the extension host a request to terminate itself
// (graceful termination)
const socket = this._protocol.getSocket();
this._protocol.send(createMessageOfType(MessageType.Terminate));
this._protocol.sendDisconnect();
this._protocol.dispose();
socket.end();
this._protocol = null;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册