diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index a77cf0e46e6..95d3b69e79d 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -197,9 +197,9 @@ class ExtensionHostProxy implements IExtensionHostProxy { resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { return this._actual.$resolveAuthority(remoteAuthority, resolveAttempt); } - async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { + async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { const uriComponents = await this._actual.$getCanonicalURI(remoteAuthority, uri); - return URI.revive(uriComponents); + return (uriComponents ? URI.revive(uriComponents) : uriComponents); } startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { return this._actual.$startExtensionHost(enabledExtensionIds); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 64f9f7de22a..73db462b209 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1339,7 +1339,10 @@ export interface ExtHostSearchShape { export interface ExtHostExtensionServiceShape { $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise; - $getCanonicalURI(remoteAuthority: string, uri: UriComponents): Promise; + /** + * Returns `null` if no resolver for `remoteAuthority` is found. + */ + $getCanonicalURI(remoteAuthority: string, uri: UriComponents): Promise; $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $extensionTestsExecute(): Promise; $extensionTestsExit(code: number): Promise; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 7d9e044afcc..3042ed465c3 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -753,12 +753,13 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } } - public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise { + public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise { this._logService.info(`$getCanonicalURI invoked for authority (${getRemoteAuthorityPrefix(remoteAuthority)})`); - const { authorityPrefix, resolver } = await this._activateAndGetResolver(remoteAuthority); + const { resolver } = await this._activateAndGetResolver(remoteAuthority); if (!resolver) { - throw new Error(`Cannot get canonical URI because no remote extension is installed to resolve ${authorityPrefix}`); + // Return `null` if no resolver for `remoteAuthority` is found. + return null; } const uri = URI.revive(uriComponents); diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index c72727c939e..ed27525eb03 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -11,7 +11,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IExtensionService, IExtensionsStatus, IExtensionHostProfile, ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, IExtensionsStatus, IExtensionHostProfile, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { append, $, Dimension, clearNode, addDisposableListener } from 'vs/base/browser/dom'; @@ -36,6 +36,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { CATEGORIES } from 'vs/workbench/common/actions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; interface IExtensionProfileInformation { /** @@ -80,6 +81,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { @IStorageService storageService: IStorageService, @ILabelService private readonly _labelService: ILabelService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IClipboardService private readonly _clipboardService: IClipboardService, ) { super(AbstractRuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); @@ -370,7 +372,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } let extraLabel: string | null = null; - if (element.status.runningLocation === ExtensionRunningLocation.LocalWebWorker) { + if (element.status.runningLocation && element.status.runningLocation.equals(new LocalWebWorkerRunningLocation())) { extraLabel = `$(globe) web worker`; } else if (element.description.extensionLocation.scheme === Schemas.vscodeRemote) { const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.remoteAuthority); @@ -379,6 +381,8 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } else { extraLabel = `$(remote) ${element.description.extensionLocation.authority}`; } + } else if (element.status.runningLocation && element.status.runningLocation.affinity > 0) { + extraLabel = `$(server-process) local process ${element.status.runningLocation.affinity + 1}`; } if (extraLabel) { @@ -427,11 +431,21 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const actions: IAction[] = []; + actions.push(new Action( + 'runtimeExtensionsEditor.action.copyId', + nls.localize('copy id', "Copy id ({0})", e.element!.description.identifier.value), + undefined, + true, + () => { + this._clipboardService.writeText(e.element!.description.identifier.value); + } + )); + const reportExtensionIssueAction = this._createReportExtensionIssueAction(e.element); if (reportExtensionIssueAction) { actions.push(reportExtensionIssueAction); - actions.push(new Separator()); } + actions.push(new Separator()); if (e.element!.marketplaceInfo) { actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo!, EnablementState.DisabledWorkspace))); diff --git a/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts index a3b1e809050..87a4c50df78 100644 --- a/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor.ts @@ -4,38 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IExtensionService, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IContextKeyService contextKeyService: IContextKeyService, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionService extensionService: IExtensionService, - @INotificationService notificationService: INotificationService, - @IContextMenuService contextMenuService: IContextMenuService, - @IInstantiationService instantiationService: IInstantiationService, - @IStorageService storageService: IStorageService, - @ILabelService labelService: ILabelService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - ) { - super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService); - } - protected _getProfileInfo(): IExtensionHostProfile | null { return null; } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts index 6787e95d718..b411342d32e 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IProductService } from 'vs/platform/product/common/productService'; import { Action } from 'vs/base/common/actions'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -29,8 +29,8 @@ export class DebugExtensionHostAction extends Action { override async run(): Promise { - const inspectPort = await this._extensionService.getInspectPort(false); - if (!inspectPort) { + const inspectPorts = await this._extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, false); + if (inspectPorts.length === 0) { const res = await this._dialogService.confirm({ type: 'info', message: nls.localize('restart1', "Profile Extensions"), @@ -45,11 +45,16 @@ export class DebugExtensionHostAction extends Action { return; } + if (inspectPorts.length > 1) { + // TODO + console.warn(`There are multiple extension hosts available for debugging. Picking the first one...`); + } + return this._debugService.startDebugging(undefined, { type: 'node', name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"), request: 'attach', - port: inspectPort + port: inspectPorts[0] }); } } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts index c6efc4ff567..44295a6868b 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionHostProfile, ProfileSession, IExtensionService, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; @@ -116,8 +116,9 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio return null; } - const inspectPort = await this._extensionService.getInspectPort(true); - if (!inspectPort) { + const inspectPorts = await this._extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, true); + + if (inspectPorts.length === 0) { return this._dialogService.confirm({ type: 'info', message: nls.localize('restart1', "Profile Extensions"), @@ -131,9 +132,14 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio }); } + if (inspectPorts.length > 1) { + // TODO + console.warn(`There are multiple extension hosts available for profiling. Picking the first one...`); + } + this._setState(ProfileSessionState.Starting); - return this._instantiationService.createInstance(ExtensionHostProfiler, inspectPort).start().then((value) => { + return this._instantiationService.createInstance(ExtensionHostProfiler, inspectPorts[0]).start().then((value) => { this._profileSession = value; this._setState(ProfileSessionState.Running); }, (err) => { diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts index e967b931728..ba9f426ab94 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IExtensionService, IResponsiveStateChangeEvent, IExtensionHostProfile, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, IResponsiveStateChangeEvent, IExtensionHostProfile, ProfileSession, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; @@ -46,8 +46,11 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont } private async _onDidChangeResponsiveChange(event: IResponsiveStateChangeEvent): Promise { + if (event.extensionHostKind !== ExtensionHostKind.LocalProcess) { + return; + } - const port = await this._extensionService.getInspectPort(true); + const port = await this._extensionService.getInspectPort(event.extensionHostId, true); if (!port) { return; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts index ca31c6145d1..b53ea1625f5 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts @@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -72,9 +73,10 @@ export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { @IStorageService storageService: IStorageService, @ILabelService labelService: ILabelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IClipboardService clipboardService: IClipboardService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, ) { - super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService); + super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService); this._profileInfo = this._extensionHostProfileService.lastProfile; this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService); this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index ff5696b45fa..3484860be18 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -9,14 +9,14 @@ import { IWorkbenchExtensionEnablementService, IWebExtensionsScannerService } fr import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IExtensionService, IExtensionHost, toExtensionDescription, ExtensionRunningLocation, extensionRunningLocationToString } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, IExtensionHost, toExtensionDescription, ExtensionRunningLocation, ExtensionHostKind, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; import { AbstractExtensionService, ExtensionRunningPreference, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import { RemoteExtensionHost, IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; +import { IWebWorkerExtensionHostDataProvider, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionIdentifier, IExtensionDescription, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ExtensionKind } from 'vs/platform/environment/common/environment'; @@ -72,8 +72,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten logService ); - this._runningLocation = new Map(); - // Initialize installed extensions first and do it only after workbench is ready this._lifecycleService.when(LifecyclePhase.Ready).then(async () => { await this._userDataInitializationService.initializeInstalledExtensions(this._instantiationService); @@ -107,11 +105,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._disposables.add(this._fileService.registerProvider(Schemas.https, provider)); } - private _createLocalExtensionHostDataProvider() { + private _createLocalExtensionHostDataProvider(desiredRunningLocation: ExtensionRunningLocation): IWebWorkerExtensionHostDataProvider { return { getInitData: async () => { const allExtensions = await this.getExtensions(); - const localWebWorkerExtensions = filterByRunningLocation(allExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker); + const localWebWorkerExtensions = this._filterByRunningLocation(allExtensions, desiredRunningLocation); return { autoStart: true, extensions: localWebWorkerExtensions @@ -130,20 +128,20 @@ export class ExtensionService extends AbstractExtensionService implements IExten }; } - protected _pickRunningLocation(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation { + protected _pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null { const result = ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference); - this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionRunningLocationToString(result)}`); + this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionHostKindToString(result)}`); return result; } - public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation { - const result: ExtensionRunningLocation[] = []; + public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null { + const result: ExtensionHostKind[] = []; let canRunRemotely = false; for (const extensionKind of extensionKinds) { if (extensionKind === 'ui' && isInstalledRemotely) { // ui extensions run remotely if possible (but only as a last resort) if (preference === ExtensionRunningPreference.Remote) { - return ExtensionRunningLocation.Remote; + return ExtensionHostKind.Remote; } else { canRunRemotely = true; } @@ -151,39 +149,42 @@ export class ExtensionService extends AbstractExtensionService implements IExten if (extensionKind === 'workspace' && isInstalledRemotely) { // workspace extensions run remotely if possible if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Remote) { - return ExtensionRunningLocation.Remote; + return ExtensionHostKind.Remote; } else { - result.push(ExtensionRunningLocation.Remote); + result.push(ExtensionHostKind.Remote); } } if (extensionKind === 'web' && (isInstalledLocally || isInstalledRemotely)) { // web worker extensions run in the local web worker if possible if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { - return ExtensionRunningLocation.LocalWebWorker; + return ExtensionHostKind.LocalWebWorker; } else { - result.push(ExtensionRunningLocation.LocalWebWorker); + result.push(ExtensionHostKind.LocalWebWorker); } } } if (canRunRemotely) { - result.push(ExtensionRunningLocation.Remote); + result.push(ExtensionHostKind.Remote); } - return (result.length > 0 ? result[0] : ExtensionRunningLocation.None); + return (result.length > 0 ? result[0] : null); } - protected _createExtensionHosts(_isInitialStart: boolean): IExtensionHost[] { - const result: IExtensionHost[] = []; - - const webWorkerExtHost = this._instantiationService.createInstance(WebWorkerExtensionHost, false, this._createLocalExtensionHostDataProvider()); - result.push(webWorkerExtHost); - - const remoteAgentConnection = this._remoteAgentService.getConnection(); - if (remoteAgentConnection) { - const remoteExtHost = this._instantiationService.createInstance(RemoteExtensionHost, this._createRemoteExtensionHostDataProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); - result.push(remoteExtHost); + protected _createExtensionHost(runningLocation: ExtensionRunningLocation, _isInitialStart: boolean): IExtensionHost | null { + switch (runningLocation.kind) { + case ExtensionHostKind.LocalProcess: { + return null; + } + case ExtensionHostKind.LocalWebWorker: { + return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, false, this._createLocalExtensionHostDataProvider(runningLocation)); + } + case ExtensionHostKind.Remote: { + const remoteAgentConnection = this._remoteAgentService.getConnection(); + if (remoteAgentConnection) { + return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); + } + return null; + } } - - return result; } protected async _scanAndHandleExtensions(): Promise { @@ -199,12 +200,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten const remoteAgentConnection = this._remoteAgentService.getConnection(); // `determineRunningLocation` will look at the complete picture (e.g. an extension installed on both sides), // takes care of duplicates and picks a running location for each extension - this._runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, remoteExtensions); + this._initializeRunningLocation(localExtensions, remoteExtensions); // Some remote extensions could run locally in the web worker, so store them - const remoteExtensionsThatNeedToRunLocally = filterByRunningLocation(remoteExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker); - localExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker); - remoteExtensions = filterByRunningLocation(remoteExtensions, this._runningLocation, ExtensionRunningLocation.Remote); + const remoteExtensionsThatNeedToRunLocally = this._filterByExtensionHostKind(remoteExtensions, ExtensionHostKind.LocalWebWorker); + localExtensions = this._filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalWebWorker); + remoteExtensions = this._filterByExtensionHostKind(remoteExtensions, ExtensionHostKind.Remote); // Add locally the remote extensions that need to run locally in the web worker for (const ext of remoteExtensionsThatNeedToRunLocally) { @@ -246,10 +247,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } -function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] { - return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === desiredRunningLocation); -} - function includes(extensions: IExtensionDescription[], identifier: ExtensionIdentifier): boolean { for (const extension of extensions) { if (ExtensionIdentifier.equals(extension.identifier, identifier)) { diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 2503a9db881..85bff25d857 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -16,7 +16,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import * as platform from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; -import { IExtensionHost, ExtensionHostLogFileName, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionHost, ExtensionHostLogFileName, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { joinPath } from 'vs/base/common/resources'; @@ -30,6 +30,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { FileAccess } from 'vs/base/common/network'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { parentOriginHash } from 'vs/workbench/browser/webview'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; export interface IWebWorkerExtensionHostInitData { readonly autoStart: boolean; @@ -42,9 +43,9 @@ export interface IWebWorkerExtensionHostDataProvider { export class WebWorkerExtensionHost extends Disposable implements IExtensionHost { - public readonly kind = ExtensionHostKind.LocalWebWorker; public readonly remoteAuthority = null; public readonly lazyStart: boolean; + public readonly extensions = new ExtensionDescriptionRegistry([]); private readonly _onDidExit = this._register(new Emitter<[number, string | null]>()); public readonly onExit: Event<[number, string | null]> = this._onDidExit.event; @@ -57,6 +58,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost private readonly _extensionHostLogFile: URI; constructor( + public readonly runningLocation: LocalWebWorkerRunningLocation, lazyStart: boolean, private readonly _initDataProvider: IWebWorkerExtensionHostDataProvider, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -265,6 +267,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost private async _createExtHostInitData(): Promise { const [telemetryInfo, initData] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]); const workspace = this._contextService.getWorkspace(); + this.extensions.deltaExtensions(initData.extensions, []); return { commit: this._productService.commit, version: this._productService.version, @@ -288,7 +291,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost }, resolvedExtensions: [], hostExtensions: [], - extensions: initData.extensions, + extensions: this.extensions.getAllExtensionDescriptions(), telemetryInfo, logLevel: this._logService.getLevel(), logsLocation: this._extensionHostLogsLocation, diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index aeb23b01d1e..ab89f991856 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -15,7 +15,7 @@ import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService } fr import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, toExtensionDescription, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, toExtensionDescription, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; @@ -132,29 +132,28 @@ export abstract class AbstractExtensionService extends Disposable implements IEx public _serviceBrand: undefined; - protected readonly _onDidRegisterExtensions: Emitter = this._register(new Emitter()); + private readonly _onDidRegisterExtensions: Emitter = this._register(new Emitter()); public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event; - protected readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); + private readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); public readonly onDidChangeExtensionsStatus: Event = this._onDidChangeExtensionsStatus.event; - protected readonly _onDidChangeExtensions: Emitter = this._register(new Emitter({ leakWarningThreshold: 400 })); + private readonly _onDidChangeExtensions: Emitter = this._register(new Emitter({ leakWarningThreshold: 400 })); public readonly onDidChangeExtensions: Event = this._onDidChangeExtensions.event; - protected readonly _onWillActivateByEvent = this._register(new Emitter()); + private readonly _onWillActivateByEvent = this._register(new Emitter()); public readonly onWillActivateByEvent: Event = this._onWillActivateByEvent.event; - protected readonly _onDidChangeResponsiveChange = this._register(new Emitter()); + private readonly _onDidChangeResponsiveChange = this._register(new Emitter()); public readonly onDidChangeResponsiveChange: Event = this._onDidChangeResponsiveChange.event; - protected readonly _runningLocationClassifier: ExtensionRunningLocationClassifier; protected readonly _registry: ExtensionDescriptionRegistry; private readonly _registryLock: Lock; private readonly _installedExtensionsReady: Barrier; - protected readonly _isDev: boolean; + private readonly _isDev: boolean; private readonly _extensionsMessages: Map; - protected readonly _allRequestedActivateEvents = new Set(); + private readonly _allRequestedActivateEvents = new Set(); private readonly _proposedApiController: ProposedApiController; private readonly _isExtensionDevHost: boolean; protected readonly _isExtensionDevTestFromCli: boolean; @@ -162,10 +161,12 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private _deltaExtensionsQueue: DeltaExtensionsQueueItem[]; private _inHandleDeltaExtensions: boolean; - protected _runningLocation: Map; + private _runningLocation: Map; + private _lastExtensionHostId: number = 0; + private _maxLocalProcessAffinity: number = 0; // --- Members used per extension host process - protected _extensionHostManagers: IExtensionHostManager[]; + private _extensionHostManagers: IExtensionHostManager[]; protected _extensionHostActiveExtensions: Map; private _extensionHostActivationTimes: Map; private _extensionHostExtensionRuntimeErrors: Map; @@ -187,11 +188,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx ) { super(); - this._runningLocationClassifier = new ExtensionRunningLocationClassifier( - (extension) => this._getExtensionKind(extension), - (extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._pickRunningLocation(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) - ); - // help the file service to activate providers by activating extensions by file system event this._register(this._fileService.onWillActivateFileSystemProvider(e => { if (e.scheme !== Schemas.vscodeRemote) { @@ -264,17 +260,247 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return this._extensionManifestPropertiesService.getExtensionKind(extensionDescription); } - protected abstract _pickRunningLocation(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation; + protected abstract _pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null; - protected _getExtensionHostManager(kind: ExtensionHostKind): IExtensionHostManager | null { + protected _getExtensionHostManagers(kind: ExtensionHostKind): IExtensionHostManager[] { + return this._extensionHostManagers.filter(extHostManager => extHostManager.kind === kind); + } + + protected _getExtensionHostManagerByRunningLocation(runningLocation: ExtensionRunningLocation): IExtensionHostManager | null { for (const extensionHostManager of this._extensionHostManagers) { - if (extensionHostManager.kind === kind) { + if (extensionHostManager.representsRunningLocation(runningLocation)) { return extensionHostManager; } } return null; } + //#region running location + + private _computeAffinity(inputExtensions: IExtensionDescription[], extensionHostKind: ExtensionHostKind, isInitialAllocation: boolean): { affinities: Map; maxAffinity: number } { + // Only analyze extensions that can execute + const extensions = new Map(); + for (const extension of inputExtensions) { + if (extension.main || extension.browser) { + extensions.set(ExtensionIdentifier.toKey(extension.identifier), extension); + } + } + // Also add existing extensions of the same kind that can execute + for (const extension of this._registry.getAllExtensionDescriptions()) { + if (extension.main || extension.browser) { + const runningLocation = this._runningLocation.get(ExtensionIdentifier.toKey(extension.identifier)); + if (runningLocation && runningLocation.kind === extensionHostKind) { + extensions.set(ExtensionIdentifier.toKey(extension.identifier), extension); + } + } + } + + // Initially, each extension belongs to its own group + const groups = new Map(); + let groupNumber = 0; + for (const [_, extension] of extensions) { + groups.set(ExtensionIdentifier.toKey(extension.identifier), ++groupNumber); + } + + const changeGroup = (from: number, to: number) => { + for (const [key, group] of groups) { + if (group === from) { + groups.set(key, to); + } + } + }; + + // We will group things together when there are dependencies + for (const [_, extension] of extensions) { + if (!extension.extensionDependencies) { + continue; + } + const myGroup = groups.get(ExtensionIdentifier.toKey(extension.identifier))!; + for (const depId of extension.extensionDependencies) { + const depGroup = groups.get(ExtensionIdentifier.toKey(depId)); + if (!depGroup) { + // probably can't execute, so it has no impact + continue; + } + + if (depGroup === myGroup) { + // already in the same group + continue; + } + + changeGroup(depGroup, myGroup); + } + } + + // Initialize with existing affinities + const resultingAffinities = new Map(); + let lastAffinity = 0; + for (const [_, extension] of extensions) { + const runningLocation = this._runningLocation.get(ExtensionIdentifier.toKey(extension.identifier)); + if (runningLocation) { + const group = groups.get(ExtensionIdentifier.toKey(extension.identifier))!; + resultingAffinities.set(group, runningLocation.affinity); + lastAffinity = Math.max(lastAffinity, runningLocation.affinity); + } + } + + // Go through each configured affinity and try to accomodate it + const configuredAffinities = this._configurationService.getValue<{ [extensionId: string]: number } | undefined>('extensions.experimental.affinity') || {}; + const configuredExtensionIds = Object.keys(configuredAffinities); + const configuredAffinityToResultingAffinity = new Map(); + for (const extensionId of configuredExtensionIds) { + const configuredAffinity = configuredAffinities[extensionId]; + if (typeof configuredAffinity !== 'number' || configuredAffinity <= 0 || Math.floor(configuredAffinity) !== configuredAffinity) { + this._logService.info(`Ignoring configured affinity for '${extensionId}' because the value is not a positive integer.`); + continue; + } + const group = groups.get(ExtensionIdentifier.toKey(extensionId)); + if (!group) { + this._logService.info(`Ignoring configured affinity for '${extensionId}' because the extension is unknown or cannot execute.`); + continue; + } + + const affinity1 = resultingAffinities.get(group); + if (affinity1) { + // Affinity for this group is already established + configuredAffinityToResultingAffinity.set(configuredAffinity, affinity1); + continue; + } + + const affinity2 = configuredAffinityToResultingAffinity.get(configuredAffinity); + if (affinity2) { + // Affinity for this configuration is already established + resultingAffinities.set(group, affinity2); + continue; + } + + if (!isInitialAllocation) { + this._logService.info(`Ignoring configured affinity for '${extensionId}' because extension host(s) are already running. Reload window.`); + continue; + } + + const affinity3 = ++lastAffinity; + configuredAffinityToResultingAffinity.set(configuredAffinity, affinity3); + resultingAffinities.set(group, affinity3); + } + + const result = new Map(); + for (const extension of inputExtensions) { + const group = groups.get(ExtensionIdentifier.toKey(extension.identifier)) || 0; + const affinity = resultingAffinities.get(group) || 0; + result.set(ExtensionIdentifier.toKey(extension.identifier), affinity); + } + + if (lastAffinity > 0 && isInitialAllocation) { + for (let affinity = 1; affinity <= lastAffinity; affinity++) { + const extensionIds: ExtensionIdentifier[] = []; + for (const extension of inputExtensions) { + if (result.get(ExtensionIdentifier.toKey(extension.identifier)) === affinity) { + extensionIds.push(extension.identifier); + } + } + this._logService.info(`Placing extension(s) ${extensionIds.map(e => e.value).join(', ')} on a separate extension host.`); + } + } + + return { affinities: result, maxAffinity: lastAffinity }; + } + + private _computeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: Map; maxLocalProcessAffinity: number } { + const extensionHostKinds = ExtensionHostKindClassifier.determineExtensionHostKinds( + localExtensions, + remoteExtensions, + (extension) => this._getExtensionKind(extension), + (extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._pickExtensionHostKind(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) + ); + + const extensions = new Map(); + for (const extension of localExtensions) { + extensions.set(ExtensionIdentifier.toKey(extension.identifier), extension); + } + for (const extension of remoteExtensions) { + extensions.set(ExtensionIdentifier.toKey(extension.identifier), extension); + } + + const result = new Map(); + const localProcessExtensions: IExtensionDescription[] = []; + for (const [extensionIdKey, extensionHostKind] of extensionHostKinds) { + let runningLocation: ExtensionRunningLocation | null = null; + if (extensionHostKind === ExtensionHostKind.LocalProcess) { + const extensionDescription = extensions.get(ExtensionIdentifier.toKey(extensionIdKey)); + if (extensionDescription) { + localProcessExtensions.push(extensionDescription); + } + } else if (extensionHostKind === ExtensionHostKind.LocalWebWorker) { + runningLocation = new LocalWebWorkerRunningLocation(); + } else if (extensionHostKind === ExtensionHostKind.Remote) { + runningLocation = new RemoteRunningLocation(); + } + result.set(extensionIdKey, runningLocation); + } + + const { affinities, maxAffinity } = this._computeAffinity(localProcessExtensions, ExtensionHostKind.LocalProcess, isInitialAllocation); + for (const extension of localProcessExtensions) { + const affinity = affinities.get(ExtensionIdentifier.toKey(extension.identifier)) || 0; + result.set(ExtensionIdentifier.toKey(extension.identifier), new LocalProcessRunningLocation(affinity)); + } + + return { runningLocation: result, maxLocalProcessAffinity: maxAffinity }; + } + + protected _determineRunningLocation(localExtensions: IExtensionDescription[]): Map { + return this._computeRunningLocation(localExtensions, [], false).runningLocation; + } + + protected _initializeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[]): void { + const { runningLocation, maxLocalProcessAffinity } = this._computeRunningLocation(localExtensions, remoteExtensions, true); + this._runningLocation = runningLocation; + this._maxLocalProcessAffinity = maxLocalProcessAffinity; + this._startExtensionHostsIfNecessary(true, []); + } + + /** + * Update `this._runningLocation` with running locations for newly enabled/installed extensions. + */ + private _updateRunningLocationForAddedExtensions(toAdd: IExtensionDescription[]): void { + // Determine new running location + const localProcessExtensions: IExtensionDescription[] = []; + for (const extension of toAdd) { + const extensionKind = this._getExtensionKind(extension); + const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote; + const extensionHostKind = this._pickExtensionHostKind(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None); + let runningLocation: ExtensionRunningLocation | null = null; + if (extensionHostKind === ExtensionHostKind.LocalProcess) { + localProcessExtensions.push(extension); + } else if (extensionHostKind === ExtensionHostKind.LocalWebWorker) { + runningLocation = new LocalWebWorkerRunningLocation(); + } else if (extensionHostKind === ExtensionHostKind.Remote) { + runningLocation = new RemoteRunningLocation(); + } + this._runningLocation.set(ExtensionIdentifier.toKey(extension.identifier), runningLocation); + } + + const { affinities } = this._computeAffinity(localProcessExtensions, ExtensionHostKind.LocalProcess, false); + for (const extension of localProcessExtensions) { + const affinity = affinities.get(ExtensionIdentifier.toKey(extension.identifier)) || 0; + this._runningLocation.set(ExtensionIdentifier.toKey(extension.identifier), new LocalProcessRunningLocation(affinity)); + } + } + + protected _filterByRunningLocation(extensions: IExtensionDescription[], desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] { + return filterByRunningLocation(extensions, this._runningLocation, desiredRunningLocation); + } + + protected _filterByExtensionHostKind(extensions: IExtensionDescription[], desiredExtensionHostKind: ExtensionHostKind): IExtensionDescription[] { + return filterByExtensionHostKind(extensions, this._runningLocation, desiredExtensionHostKind); + } + + protected _filterByExtensionHostManager(extensions: IExtensionDescription[], extensionHostManager: IExtensionHostManager): IExtensionDescription[] { + return filterByExtensionHostManager(extensions, this._runningLocation, extensionHostManager); + } + + //#endregion + //#region deltaExtensions private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise { @@ -374,47 +600,32 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { - const groupedToRemove: ExtensionIdentifier[][] = []; - const groupRemove = (extensionHostKind: ExtensionHostKind, extensionRunningLocation: ExtensionRunningLocation) => { - groupedToRemove[extensionHostKind] = filterByRunningLocation(toRemove, extId => extId, this._runningLocation, extensionRunningLocation); - }; - groupRemove(ExtensionHostKind.LocalProcess, ExtensionRunningLocation.LocalProcess); - groupRemove(ExtensionHostKind.LocalWebWorker, ExtensionRunningLocation.LocalWebWorker); - groupRemove(ExtensionHostKind.Remote, ExtensionRunningLocation.Remote); + + // Remove old running location + const removedRunningLocation = new Map(); for (const extensionId of toRemove) { - this._runningLocation.delete(ExtensionIdentifier.toKey(extensionId)); + const extensionKey = ExtensionIdentifier.toKey(extensionId); + removedRunningLocation.set(extensionKey, this._runningLocation.get(extensionKey) || null); + this._runningLocation.delete(extensionKey); } - const groupedToAdd: IExtensionDescription[][] = []; - const groupAdd = (extensionHostKind: ExtensionHostKind, extensionRunningLocation: ExtensionRunningLocation) => { - groupedToAdd[extensionHostKind] = filterByRunningLocation(toAdd, ext => ext.identifier, this._runningLocation, extensionRunningLocation); - }; - for (const extension of toAdd) { - const extensionKind = this._getExtensionKind(extension); - const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote; - const runningLocation = this._pickRunningLocation(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None); - this._runningLocation.set(ExtensionIdentifier.toKey(extension.identifier), runningLocation); - } - groupAdd(ExtensionHostKind.LocalProcess, ExtensionRunningLocation.LocalProcess); - groupAdd(ExtensionHostKind.LocalWebWorker, ExtensionRunningLocation.LocalWebWorker); - groupAdd(ExtensionHostKind.Remote, ExtensionRunningLocation.Remote); - - const promises: Promise[] = []; - - for (const extensionHostKind of [ExtensionHostKind.LocalProcess, ExtensionHostKind.LocalWebWorker, ExtensionHostKind.Remote]) { - const toAdd = groupedToAdd[extensionHostKind]; - const toRemove = groupedToRemove[extensionHostKind]; - if (toAdd.length > 0 || toRemove.length > 0) { - const extensionHostManager = this._getExtensionHostManager(extensionHostKind); - if (extensionHostManager) { - promises.push(extensionHostManager.deltaExtensions(toAdd, toRemove)); - } - } - } + // Determine new running location + this._updateRunningLocationForAddedExtensions(toAdd); + const promises = this._extensionHostManagers.map( + extHostManager => this._updateExtensionsOnExtHost(extHostManager, toAdd, toRemove, removedRunningLocation) + ); await Promise.all(promises); } + private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, _toAdd: IExtensionDescription[], _toRemove: ExtensionIdentifier[], removedRunningLocation: Map): Promise { + const toAdd = filterByExtensionHostManager(_toAdd, this._runningLocation, extensionHostManager); + const toRemove = _filterByExtensionHostManager(_toRemove, extId => extId, removedRunningLocation, extensionHostManager); + if (toRemove.length > 0 || toAdd.length > 0) { + await extensionHostManager.deltaExtensions(toAdd, toRemove); + } + } + public canAddExtension(extension: IExtensionDescription): boolean { const existing = this._registry.getExtensionDescription(extension.identifier); if (existing) { @@ -429,8 +640,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const extensionKind = this._getExtensionKind(extension); const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote; - const runningLocation = this._pickRunningLocation(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None); - if (runningLocation === ExtensionRunningLocation.None) { + const extensionHostKind = this._pickExtensionHostKind(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None); + if (extensionHostKind === null) { return false; } @@ -518,7 +729,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx protected async _initialize(): Promise { perf.mark('code/willLoadExtensions'); - this._startExtensionHosts(true, []); + this._startExtensionHostsIfNecessary(true, []); const lock = await this._registryLock.acquire('_initialize'); try { @@ -558,38 +769,31 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._onExtensionHostExit(exitCode); } - private findTestExtensionHost(testLocation: URI): IExtensionHostManager | undefined | null { - let extensionHostKind: ExtensionHostKind | undefined; + private findTestExtensionHost(testLocation: URI): IExtensionHostManager | null { + let runningLocation: ExtensionRunningLocation | null = null; for (const extension of this._registry.getAllExtensionDescriptions()) { if (isEqualOrParent(testLocation, extension.extensionLocation)) { - const runningLocation = this._runningLocation.get(ExtensionIdentifier.toKey(extension.identifier)); - if (runningLocation === ExtensionRunningLocation.LocalProcess) { - extensionHostKind = ExtensionHostKind.LocalProcess; - } else if (runningLocation === ExtensionRunningLocation.LocalWebWorker) { - extensionHostKind = ExtensionHostKind.LocalWebWorker; - } else if (runningLocation === ExtensionRunningLocation.Remote) { - extensionHostKind = ExtensionHostKind.Remote; - } + runningLocation = this._runningLocation.get(ExtensionIdentifier.toKey(extension.identifier)) || null; break; } } - if (extensionHostKind === undefined) { + if (runningLocation === null) { // not sure if we should support that, but it was possible to have an test outside an extension if (testLocation.scheme === Schemas.vscodeRemote) { - extensionHostKind = ExtensionHostKind.Remote; + runningLocation = new RemoteRunningLocation(); } else { // When a debugger attaches to the extension host, it will surface all console.log messages from the extension host, // but not necessarily from the window. So it would be best if any errors get printed to the console of the extension host. // That is why here we use the local process extension host even for non-file URIs - extensionHostKind = ExtensionHostKind.LocalProcess; + runningLocation = new LocalProcessRunningLocation(0); } } - if (extensionHostKind !== undefined) { - return this._getExtensionHostManager(extensionHostKind); + if (runningLocation !== null) { + return this._getExtensionHostManagerByRunningLocation(runningLocation); } - return undefined; + return null; } private _releaseBarrier(): void { @@ -619,14 +823,42 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - private _startExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): void { - const extensionHosts = this._createExtensionHosts(isInitialStart); - extensionHosts.forEach((extensionHost) => { - const processManager: IExtensionHostManager = createExtensionHostManager(this._instantiationService, extensionHost, isInitialStart, initialActivationEvents, this._acquireInternalAPI()); - processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal)); - processManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); - this._extensionHostManagers.push(processManager); + private _startExtensionHostsIfNecessary(isInitialStart: boolean, initialActivationEvents: string[]): void { + const locations: ExtensionRunningLocation[] = []; + for (let affinity = 0; affinity <= this._maxLocalProcessAffinity; affinity++) { + locations.push(new LocalProcessRunningLocation(affinity)); + } + locations.push(new LocalWebWorkerRunningLocation()); + locations.push(new RemoteRunningLocation()); + for (const location of locations) { + if (this._getExtensionHostManagerByRunningLocation(location)) { + // already running + continue; + } + const extHostManager = this._createExtensionHostManager(location, isInitialStart, initialActivationEvents); + if (extHostManager) { + this._extensionHostManagers.push(extHostManager); + } + } + } + + private _createExtensionHostManager(runningLocation: ExtensionRunningLocation, isInitialStart: boolean, initialActivationEvents: string[]): IExtensionHostManager | null { + const extensionHost = this._createExtensionHost(runningLocation, isInitialStart); + if (!extensionHost) { + return null; + } + + const extensionHostId = String(++this._lastExtensionHostId); + const processManager: IExtensionHostManager = createExtensionHostManager(this._instantiationService, extensionHostId, extensionHost, isInitialStart, initialActivationEvents, this._acquireInternalAPI()); + processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal)); + processManager.onDidChangeResponsiveState((responsiveState) => { + this._onDidChangeResponsiveChange.fire({ + extensionHostId: extensionHostId, + extensionHostKind: processManager.kind, + isResponsive: responsiveState === ResponsiveState.Responsive + }); }); + return processManager; } private _onExtensionHostCrashOrExit(extensionHost: IExtensionHostManager, code: number, signal: string | null): void { @@ -660,12 +892,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const lock = await this._registryLock.acquire('startExtensionHosts'); try { - this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys())); + this._startExtensionHostsIfNecessary(false, Array.from(this._allRequestedActivateEvents.keys())); - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess); - if (localProcessExtensionHost) { - await localProcessExtensionHost.ready(); - } + const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess); + await Promise.all(localProcessExtensionHosts.map(extHost => extHost.ready())); } finally { lock.dispose(); } @@ -771,15 +1001,28 @@ export abstract class AbstractExtensionService extends Disposable implements IEx messages: this._extensionsMessages.get(extensionKey) || [], activationTimes: this._extensionHostActivationTimes.get(extensionKey), runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [], - runningLocation: this._runningLocation.get(extensionKey) || ExtensionRunningLocation.None, + runningLocation: this._runningLocation.get(extensionKey) || null, }; } } return result; } - public getInspectPort(_tryEnableInspector: boolean): Promise { - return Promise.resolve(0); + public async getInspectPort(extensionHostId: string, tryEnableInspector: boolean): Promise { + for (const extHostManager of this._extensionHostManagers) { + if (extHostManager.extensionHostId === extensionHostId) { + return extHostManager.getInspectPort(tryEnableInspector); + } + } + return 0; + } + + public async getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise { + const result = await Promise.all( + this._getExtensionHostManagers(extensionHostKind).map(extHost => extHost.getInspectPort(tryEnableInspector)) + ); + // remove 0s: + return result.filter(element => Boolean(element)); } public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { @@ -1040,7 +1283,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx //#endregion - protected abstract _createExtensionHosts(isInitialStart: boolean): IExtensionHost[]; + protected abstract _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null; protected abstract _scanAndHandleExtensions(): Promise; protected abstract _scanSingleExtension(extension: IExtension): Promise; public abstract _onExtensionHostExit(code: number): void; @@ -1094,25 +1337,28 @@ class ExtensionInfo { } } -class ExtensionRunningLocationClassifier { - constructor( - private readonly getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[], - private readonly pickRunningLocation: (extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference) => ExtensionRunningLocation, - ) { - } +class ExtensionHostKindClassifier { - private _toExtensionWithKind(extensions: IExtensionDescription[]): Map { + private static _toExtensionWithKind( + extensions: IExtensionDescription[], + getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[] + ): Map { const result = new Map(); extensions.forEach((desc) => { - const ext = new ExtensionWithKind(desc, this.getExtensionKind(desc)); + const ext = new ExtensionWithKind(desc, getExtensionKind(desc)); result.set(ext.key, ext); }); return result; } - public determineRunningLocation(_localExtensions: IExtensionDescription[], _remoteExtensions: IExtensionDescription[]): Map { - const localExtensions = this._toExtensionWithKind(_localExtensions); - const remoteExtensions = this._toExtensionWithKind(_remoteExtensions); + public static determineExtensionHostKinds( + _localExtensions: IExtensionDescription[], + _remoteExtensions: IExtensionDescription[], + getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[], + pickExtensionHostKind: (extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference) => ExtensionHostKind | null + ): Map { + const localExtensions = this._toExtensionWithKind(_localExtensions, getExtensionKind); + const remoteExtensions = this._toExtensionWithKind(_remoteExtensions, getExtensionKind); const allExtensions = new Map(); const collectExtension = (ext: ExtensionWithKind) => { @@ -1127,7 +1373,7 @@ class ExtensionRunningLocationClassifier { localExtensions.forEach((ext) => collectExtension(ext)); remoteExtensions.forEach((ext) => collectExtension(ext)); - const runningLocation = new Map(); + const extensionHostKinds = new Map(); allExtensions.forEach((ext) => { const isInstalledLocally = Boolean(ext.local); const isInstalledRemotely = Boolean(ext.remote); @@ -1142,10 +1388,10 @@ class ExtensionRunningLocationClassifier { preference = ExtensionRunningPreference.Remote; } - runningLocation.set(ext.key, this.pickRunningLocation(ext.identifier, ext.kind, isInstalledLocally, isInstalledRemotely, preference)); + extensionHostKinds.set(ext.key, pickExtensionHostKind(ext.identifier, ext.kind, isInstalledLocally, isInstalledRemotely, preference)); }); - return runningLocation; + return extensionHostKinds; } } @@ -1244,6 +1490,29 @@ class ProposedApiController { } } -function filterByRunningLocation(extensions: T[], extId: (item: T) => ExtensionIdentifier, runningLocation: Map, desiredRunningLocation: ExtensionRunningLocation): T[] { - return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(extId(ext))) === desiredRunningLocation); +export function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] { + return _filterByRunningLocation(extensions, ext => ext.identifier, runningLocation, desiredRunningLocation); +} + +function _filterByRunningLocation(extensions: T[], extId: (item: T) => ExtensionIdentifier, runningLocation: Map, desiredRunningLocation: ExtensionRunningLocation): T[] { + return _filterExtensions(extensions, extId, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); +} + +function filterByExtensionHostKind(extensions: IExtensionDescription[], runningLocation: Map, desiredExtensionHostKind: ExtensionHostKind): IExtensionDescription[] { + return _filterExtensions(extensions, ext => ext.identifier, runningLocation, extRunningLocation => extRunningLocation.kind === desiredExtensionHostKind); +} + +function filterByExtensionHostManager(extensions: IExtensionDescription[], runningLocation: Map, extensionHostManager: IExtensionHostManager): IExtensionDescription[] { + return _filterByExtensionHostManager(extensions, ext => ext.identifier, runningLocation, extensionHostManager); +} + +function _filterByExtensionHostManager(extensions: T[], extId: (item: T) => ExtensionIdentifier, runningLocation: Map, extensionHostManager: IExtensionHostManager): T[] { + return _filterExtensions(extensions, extId, runningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation)); +} + +function _filterExtensions(extensions: T[], extId: (item: T) => ExtensionIdentifier, runningLocation: Map, predicate: (extRunningLocation: ExtensionRunningLocation) => boolean): T[] { + return extensions.filter((ext) => { + const extRunningLocation = runningLocation.get(ExtensionIdentifier.toKey(extId(ext))); + return extRunningLocation && predicate(extRunningLocation); + }); } diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 72400588d16..c809d5a4b5a 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -12,49 +12,54 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { ExtHostCustomersRegistry, IInternalExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { Proxied, ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as nls from 'vs/nls'; import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StopWatch } from 'vs/base/common/stopwatch'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IExtensionHost, ExtensionHostKind, ActivationKind, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionHost, ExtensionHostKind, ActivationKind, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { Barrier, timeout } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IExtensionHostProxy } from 'vs/workbench/services/extensions/common/extensionHostProxy'; +import { IExtensionHostProxy, IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; const LOG_USE_COLORS = true; export interface IExtensionHostManager { + readonly extensionHostId: string; readonly kind: ExtensionHostKind; readonly onDidExit: Event<[number, string | null]>; readonly onDidChangeResponsiveState: Event; dispose(): void; ready(): Promise; + representsRunningLocation(runningLocation: ExtensionRunningLocation): boolean; deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise; activationEventIsDone(activationEvent: string): boolean; getInspectPort(tryEnableInspector: boolean): Promise; - resolveAuthority(remoteAuthority: string): Promise; - getCanonicalURI(remoteAuthority: string, uri: URI): Promise; + resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise; + /** + * Returns `null` if no resolver for `remoteAuthority` is found. + */ + getCanonicalURI(remoteAuthority: string, uri: URI): Promise; start(enabledExtensionIds: ExtensionIdentifier[]): Promise; extensionTestsExecute(): Promise; extensionTestsSendExit(exitCode: number): Promise; setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } -export function createExtensionHostManager(instantiationService: IInstantiationService, extensionHost: IExtensionHost, isInitialStart: boolean, initialActivationEvents: string[], internalExtensionService: IInternalExtensionService): IExtensionHostManager { +export function createExtensionHostManager(instantiationService: IInstantiationService, extensionHostId: string, extensionHost: IExtensionHost, isInitialStart: boolean, initialActivationEvents: string[], internalExtensionService: IInternalExtensionService): IExtensionHostManager { if (extensionHost.lazyStart && isInitialStart && initialActivationEvents.length === 0) { - return instantiationService.createInstance(LazyStartExtensionHostManager, extensionHost, internalExtensionService); + return instantiationService.createInstance(LazyStartExtensionHostManager, extensionHostId, extensionHost, internalExtensionService); } - return instantiationService.createInstance(ExtensionHostManager, extensionHost, initialActivationEvents, internalExtensionService); + return instantiationService.createInstance(ExtensionHostManager, extensionHostId, extensionHost, initialActivationEvents, internalExtensionService); } export type ExtensionHostStartupClassification = { @@ -77,7 +82,6 @@ export type ExtensionHostStartupEvent = { class ExtensionHostManager extends Disposable implements IExtensionHostManager { - public readonly kind: ExtensionHostKind; public readonly onDidExit: Event<[number, string | null]>; private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); @@ -92,10 +96,14 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { private readonly _customers: IDisposable[]; private readonly _extensionHost: IExtensionHost; private _proxy: Promise | null; - private _resolveAuthorityAttempt: number; private _hasStarted = false; + public get kind(): ExtensionHostKind { + return this._extensionHost.runningLocation.kind; + } + constructor( + public readonly extensionHostId: string, extensionHost: IExtensionHost, initialActivationEvents: string[], private readonly _internalExtensionService: IInternalExtensionService, @@ -111,7 +119,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { this._customers = []; this._extensionHost = extensionHost; - this.kind = this._extensionHost.kind; this.onDidExit = this._extensionHost.onExit; const startingTelemetryEvent: ExtensionHostStartupEvent = { @@ -166,7 +173,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { measure: () => this.measure() })); }); - this._resolveAuthorityAttempt = 0; } public override dispose(): void { @@ -190,7 +196,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } private async measure(): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { return null; } @@ -205,12 +211,8 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { }; } - private async _getProxy(): Promise { - return this._proxy; - } - public async ready(): Promise { - await this._getProxy(); + await this._proxy; } private async _measureLatency(proxy: IExtensionHostProxy): Promise { @@ -310,7 +312,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { return false; } @@ -359,57 +361,52 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { return 0; } - public async resolveAuthority(remoteAuthority: string): Promise { - const authorityPlusIndex = remoteAuthority.indexOf('+'); - if (authorityPlusIndex === -1) { - // This authority does not need to be resolved, simply parse the port number - const lastColon = remoteAuthority.lastIndexOf(':'); - return Promise.resolve({ - authority: { - authority: remoteAuthority, - host: remoteAuthority.substring(0, lastColon), - port: parseInt(remoteAuthority.substring(lastColon + 1), 10), - connectionToken: undefined - } - }); - } - const proxy = await this._getProxy(); + public async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { + const proxy = await this._proxy; if (!proxy) { - throw new Error(`Cannot resolve authority`); + return { + type: 'error', + error: { + message: `Cannot resolve authority`, + code: RemoteAuthorityResolverErrorCode.Unknown, + detail: undefined + } + }; } - this._resolveAuthorityAttempt++; - const result = await proxy.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt); - if (result.type === 'ok') { - return result.value; - } else { - throw new RemoteAuthorityResolverError(result.error.message, result.error.code, result.error.detail); + + try { + return proxy.resolveAuthority(remoteAuthority, resolveAttempt); + } catch (err) { + return { + type: 'error', + error: { + message: err.message, + code: RemoteAuthorityResolverErrorCode.Unknown, + detail: err + } + }; } } - public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { - const authorityPlusIndex = remoteAuthority.indexOf('+'); - if (authorityPlusIndex === -1) { - // This authority does not use a resolver - return uri; - } - const proxy = await this._getProxy(); + public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { + const proxy = await this._proxy; if (!proxy) { throw new Error(`Cannot resolve canonical URI`); } - const result = await proxy.getCanonicalURI(remoteAuthority, uri); - return URI.revive(result); + return proxy.getCanonicalURI(remoteAuthority, uri); } public async start(enabledExtensionIds: ExtensionIdentifier[]): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { return; } + this._extensionHost.extensions.keepOnly(enabledExtensionIds); return proxy.startExtensionHost(enabledExtensionIds); } public async extensionTestsExecute(): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { throw new Error('Could not obtain Extension Host Proxy'); } @@ -417,7 +414,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } public async extensionTestsSendExit(exitCode: number): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { return; } @@ -430,16 +427,21 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { } } + public representsRunningLocation(runningLocation: ExtensionRunningLocation): boolean { + return this._extensionHost.runningLocation.equals(runningLocation); + } + public async deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { return; } + this._extensionHost.extensions.deltaExtensions(toAdd, toRemove); return proxy.deltaExtensions(toAdd, toRemove); } public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { - const proxy = await this._getProxy(); + const proxy = await this._proxy; if (!proxy) { return; } @@ -452,7 +454,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { * Waits until `start()` and only if it has extensions proceeds to really start. */ class LazyStartExtensionHostManager extends Disposable implements IExtensionHostManager { - public readonly kind: ExtensionHostKind; + public readonly onDidExit: Event<[number, string | null]>; private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); public readonly onDidChangeResponsiveState: Event = this._onDidChangeResponsiveState.event; @@ -461,7 +463,12 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost private _startCalled: Barrier; private _actual: ExtensionHostManager | null; + public get kind(): ExtensionHostKind { + return this._extensionHost.runningLocation.kind; + } + constructor( + public readonly extensionHostId: string, extensionHost: IExtensionHost, private readonly _internalExtensionService: IInternalExtensionService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -469,7 +476,6 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost ) { super(); this._extensionHost = extensionHost; - this.kind = extensionHost.kind; this.onDidExit = extensionHost.onExit; this._startCalled = new Barrier(); this._actual = null; @@ -477,7 +483,7 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost private _createActual(reason: string): ExtensionHostManager { this._logService.info(`Creating lazy extension host: ${reason}`); - this._actual = this._register(this._instantiationService.createInstance(ExtensionHostManager, this._extensionHost, [], this._internalExtensionService)); + this._actual = this._register(this._instantiationService.createInstance(ExtensionHostManager, this.extensionHostId, this._extensionHost, [], this._internalExtensionService)); this._register(this._actual.onDidChangeResponsiveState((e) => this._onDidChangeResponsiveState.fire(e))); return this._actual; } @@ -498,6 +504,9 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost await this._actual.ready(); } } + public representsRunningLocation(runningLocation: ExtensionRunningLocation): boolean { + return this._extensionHost.runningLocation.equals(runningLocation); + } public async deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { await this._startCalled.wait(); const extensionHostAlreadyStarted = Boolean(this._actual); @@ -543,14 +552,21 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost } return 0; } - public async resolveAuthority(remoteAuthority: string): Promise { + public async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { await this._startCalled.wait(); if (this._actual) { - return this._actual.resolveAuthority(remoteAuthority); + return this._actual.resolveAuthority(remoteAuthority, resolveAttempt); } - throw new Error(`Cannot resolve authority`); + return { + type: 'error', + error: { + message: `Cannot resolve authority`, + code: RemoteAuthorityResolverErrorCode.Unknown, + detail: undefined + } + }; } - public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { + public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise { await this._startCalled.wait(); if (this._actual) { return this._actual.getCanonicalURI(remoteAuthority, uri); diff --git a/src/vs/workbench/services/extensions/common/extensionHostProxy.ts b/src/vs/workbench/services/extensions/common/extensionHostProxy.ts index f3acf4420a0..d021dda0f0d 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProxy.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProxy.ts @@ -27,7 +27,10 @@ export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAut export interface IExtensionHostProxy { resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise; - getCanonicalURI(remoteAuthority: string, uri: URI): Promise; + /** + * Returns `null` if no resolver for `remoteAuthority` is found. + */ + getCanonicalURI(remoteAuthority: string, uri: URI): Promise; startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; extensionTestsExecute(): Promise; extensionTestsExit(code: number): Promise; diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 20be37510fc..66de92c51a8 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -13,6 +13,7 @@ import { getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionMana import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; export const nullExtensionDescription = Object.freeze({ identifier: new ExtensionIdentifier('nullExtensionDescription'), @@ -36,31 +37,48 @@ export interface IMessage { extensionPointId: string; } -export const enum ExtensionRunningLocation { - None, - LocalProcess, - LocalWebWorker, - Remote -} - -export function extensionRunningLocationToString(location: ExtensionRunningLocation) { - switch (location) { - case ExtensionRunningLocation.None: - return 'None'; - case ExtensionRunningLocation.LocalProcess: +export class LocalProcessRunningLocation { + public readonly kind = ExtensionHostKind.LocalProcess; + constructor( + public readonly affinity: number + ) { } + public equals(other: ExtensionRunningLocation) { + return (this.kind === other.kind && this.affinity === other.affinity); + } + public asString(): string { + if (this.affinity === 0) { return 'LocalProcess'; - case ExtensionRunningLocation.LocalWebWorker: - return 'LocalWebWorker'; - case ExtensionRunningLocation.Remote: - return 'Remote'; + } + return `LocalProcess${this.affinity}`; } } +export class LocalWebWorkerRunningLocation { + public readonly kind = ExtensionHostKind.LocalWebWorker; + public readonly affinity = 0; + public equals(other: ExtensionRunningLocation) { + return (this.kind === other.kind); + } + public asString(): string { + return 'LocalWebWorker'; + } +} +export class RemoteRunningLocation { + public readonly kind = ExtensionHostKind.Remote; + public readonly affinity = 0; + public equals(other: ExtensionRunningLocation) { + return (this.kind === other.kind); + } + public asString(): string { + return 'Remote'; + } +} +export type ExtensionRunningLocation = LocalProcessRunningLocation | LocalWebWorkerRunningLocation | RemoteRunningLocation; export interface IExtensionsStatus { messages: IMessage[]; activationTimes: ActivationTimes | undefined; runtimeErrors: Error[]; - runningLocation: ExtensionRunningLocation; + runningLocation: ExtensionRunningLocation | null; } export class MissingExtensionDependency { @@ -108,12 +126,15 @@ export interface IExtensionHostProfile { } export const enum ExtensionHostKind { - LocalProcess, - LocalWebWorker, - Remote + LocalProcess = 1, + LocalWebWorker = 2, + Remote = 3 } -export function extensionHostKindToString(kind: ExtensionHostKind): string { +export function extensionHostKindToString(kind: ExtensionHostKind | null): string { + if (kind === null) { + return 'None'; + } switch (kind) { case ExtensionHostKind.LocalProcess: return 'LocalProcess'; case ExtensionHostKind.LocalWebWorker: return 'LocalWebWorker'; @@ -122,9 +143,14 @@ export function extensionHostKindToString(kind: ExtensionHostKind): string { } export interface IExtensionHost { - readonly kind: ExtensionHostKind; + readonly runningLocation: ExtensionRunningLocation; readonly remoteAuthority: string | null; readonly lazyStart: boolean; + /** + * A collection of extensions that will execute or are executing on this extension host. + * **NOTE**: this will reflect extensions correctly only after `start()` resolves. + */ + readonly extensions: ExtensionDescriptionRegistry; readonly onExit: Event<[number, string | null]>; start(): Promise | null; @@ -186,6 +212,8 @@ export interface IWillActivateEvent { } export interface IResponsiveStateChangeEvent { + extensionHostId: string; + extensionHostKind: ExtensionHostKind; isResponsive: boolean; } @@ -288,10 +316,15 @@ export interface IExtensionService { getExtensionsStatus(): { [id: string]: IExtensionsStatus }; /** - * Return the inspect port or `0`, the latter means inspection - * is not possible. + * Return the inspect port or `0` for a certain extension host. + * `0` means inspection is not possible. */ - getInspectPort(tryEnableInspector: boolean): Promise; + getInspectPort(extensionHostId: string, tryEnableInspector: boolean): Promise; + + /** + * Return the inspect ports (if inspection is possible) for extension hosts of kind `extensionHostKind`. + */ + getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise; /** * Stops the extension hosts. @@ -372,7 +405,8 @@ export class NullExtensionService implements IExtensionService { getExtension() { return Promise.resolve(undefined); } readExtensionPointContributions(_extPoint: IExtensionPoint): Promise[]> { return Promise.resolve(Object.create(null)); } getExtensionsStatus(): { [id: string]: IExtensionsStatus } { return Object.create(null); } - getInspectPort(_tryEnableInspector: boolean): Promise { return Promise.resolve(0); } + getInspectPort(_extensionHostId: string, _tryEnableInspector: boolean): Promise { return Promise.resolve(0); } + getInspectPorts(_extensionHostKind: ExtensionHostKind, _tryEnableInspector: boolean): Promise { return Promise.resolve([]); } stopExtensionHosts(): void { } async restartExtensionHost(): Promise { } async startExtensionHosts(): Promise { } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 91def9ac7da..ba239efb719 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -25,9 +25,10 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { createMessageOfType, isMessageOfType, MessageType, IExtensionHostInitData, UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; -import { ExtensionHostKind, ExtensionHostLogFileName, IExtensionHost } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostLogFileName, IExtensionHost, RemoteRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output'; @@ -49,9 +50,9 @@ export interface IRemoteExtensionHostDataProvider { export class RemoteExtensionHost extends Disposable implements IExtensionHost { - public readonly kind = ExtensionHostKind.Remote; public readonly remoteAuthority: string; public readonly lazyStart = false; + public readonly extensions = new ExtensionDescriptionRegistry([]); private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); public readonly onExit: Event<[number, string | null]> = this._onExit.event; @@ -62,6 +63,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { private readonly _isExtensionDevHost: boolean; constructor( + public readonly runningLocation: RemoteRunningLocation, private readonly _initDataProvider: IRemoteExtensionHostDataProvider, private readonly _socketFactory: ISocketFactory, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @@ -222,6 +224,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { .filter(extension => (extension.main || extension.browser) && extension.api === 'none').map(extension => extension.identifier) ); const workspace = this._contextService.getWorkspace(); + this.extensions.deltaExtensions(remoteInitData.extensions, []); return { commit: this._productService.commit, version: this._productService.version, @@ -251,7 +254,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { }, resolvedExtensions: resolvedExtensions, hostExtensions: hostExtensions, - extensions: remoteInitData.extensions, + extensions: this.extensions.getAllExtensionDescriptions(), telemetryInfo, logLevel: this._logService.getLevel(), logsLocation: remoteInitData.extensionHostLogsPath, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 783fe97db1d..241c06d4f07 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/localProcessExtensionHost'; +import { ILocalProcessExtensionHostDataProvider, LocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/localProcessExtensionHost'; import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { AbstractExtensionService, ExtensionRunningPreference, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { AbstractExtensionService, ExtensionRunningPreference, extensionRunningPreferenceToString, filterByRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import * as nls from 'vs/nls'; import { runWhenIdle } from 'vs/base/common/async'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -16,13 +16,13 @@ import { IWorkbenchExtensionEnablementService, EnablementState, IWebExtensionsSc import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig, ExtensionRunningLocation, WebWorkerExtHostConfigValue, extensionRunningLocationToString, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig, ExtensionRunningLocation, WebWorkerExtHostConfigValue, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionKind } from 'vs/platform/environment/common/environment'; @@ -35,7 +35,7 @@ import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remo import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; +import { IWebWorkerExtensionHostDataProvider, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILogService } from 'vs/platform/log/common/log'; import { CATEGORIES } from 'vs/workbench/common/actions'; @@ -48,6 +48,8 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w import { CancellationToken } from 'vs/base/common/cancellation'; import { StopWatch } from 'vs/base/common/stopwatch'; import { isCI } from 'vs/base/common/platform'; +import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; +import { URI } from 'vs/base/common/uri'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -56,6 +58,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten private readonly _remoteInitData: Map; private readonly _extensionScanner: CachedExtensionScanner; private readonly _crashTracker = new ExtensionHostCrashTracker(); + private _resolveAuthorityAttempt: number = 0; constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -151,13 +154,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten ])); } - private _createLocalExtensionHostDataProvider(isInitialStart: boolean, desiredRunningLocation: ExtensionRunningLocation) { + private _createLocalExtensionHostDataProvider(isInitialStart: boolean, desiredRunningLocation: ExtensionRunningLocation): ILocalProcessExtensionHostDataProvider & IWebWorkerExtensionHostDataProvider { return { getInitData: async () => { if (isInitialStart) { // Here we load even extensions that would be disabled by workspace trust const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions(), /* ignore workspace trust */true); - const runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, []); + const runningLocation = this._determineRunningLocation(localExtensions); const localProcessExtensions = filterByRunningLocation(localExtensions, runningLocation, desiredRunningLocation); return { autoStart: false, @@ -166,7 +169,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } else { // restart case const allExtensions = await this.getExtensions(); - const localProcessExtensions = filterByRunningLocation(allExtensions, this._runningLocation, desiredRunningLocation); + const localProcessExtensions = this._filterByRunningLocation(allExtensions, desiredRunningLocation); return { autoStart: true, extensions: localProcessExtensions @@ -186,69 +189,70 @@ export class ExtensionService extends AbstractExtensionService implements IExten }; } - protected _pickRunningLocation(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation { - const result = ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker); - this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionRunningLocationToString(result)}`); + protected _pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null { + const result = ExtensionService.pickExtensionHostKind(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker); + this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionHostKindToString(result)}`); return result; } - public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference, hasRemoteExtHost: boolean, hasWebWorkerExtHost: boolean): ExtensionRunningLocation { - const result: ExtensionRunningLocation[] = []; + public static pickExtensionHostKind(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference, hasRemoteExtHost: boolean, hasWebWorkerExtHost: boolean): ExtensionHostKind | null { + const result: ExtensionHostKind[] = []; for (const extensionKind of extensionKinds) { if (extensionKind === 'ui' && isInstalledLocally) { // ui extensions run locally if possible if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { - return ExtensionRunningLocation.LocalProcess; + return ExtensionHostKind.LocalProcess; } else { - result.push(ExtensionRunningLocation.LocalProcess); + result.push(ExtensionHostKind.LocalProcess); } } if (extensionKind === 'workspace' && isInstalledRemotely) { // workspace extensions run remotely if possible if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Remote) { - return ExtensionRunningLocation.Remote; + return ExtensionHostKind.Remote; } else { - result.push(ExtensionRunningLocation.Remote); + result.push(ExtensionHostKind.Remote); } } if (extensionKind === 'workspace' && !hasRemoteExtHost) { // workspace extensions also run locally if there is no remote if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { - return ExtensionRunningLocation.LocalProcess; + return ExtensionHostKind.LocalProcess; } else { - result.push(ExtensionRunningLocation.LocalProcess); + result.push(ExtensionHostKind.LocalProcess); } } if (extensionKind === 'web' && isInstalledLocally && hasWebWorkerExtHost) { // web worker extensions run in the local web worker if possible if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) { - return ExtensionRunningLocation.LocalWebWorker; + return ExtensionHostKind.LocalWebWorker; } else { - result.push(ExtensionRunningLocation.LocalWebWorker); + result.push(ExtensionHostKind.LocalWebWorker); } } } - return (result.length > 0 ? result[0] : ExtensionRunningLocation.None); + return (result.length > 0 ? result[0] : null); } - protected _createExtensionHosts(isInitialStart: boolean): IExtensionHost[] { - const result: IExtensionHost[] = []; - - const localProcessExtHost = this._instantiationService.createInstance(LocalProcessExtensionHost, this._createLocalExtensionHostDataProvider(isInitialStart, ExtensionRunningLocation.LocalProcess)); - result.push(localProcessExtHost); - - if (this._enableLocalWebWorker) { - const webWorkerExtHost = this._instantiationService.createInstance(WebWorkerExtensionHost, this._lazyLocalWebWorker, this._createLocalExtensionHostDataProvider(isInitialStart, ExtensionRunningLocation.LocalWebWorker)); - result.push(webWorkerExtHost); + protected _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { + switch (runningLocation.kind) { + case ExtensionHostKind.LocalProcess: { + return this._instantiationService.createInstance(LocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); + } + case ExtensionHostKind.LocalWebWorker: { + if (this._enableLocalWebWorker) { + return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, this._lazyLocalWebWorker, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); + } + return null; + } + case ExtensionHostKind.Remote: { + const remoteAgentConnection = this._remoteAgentService.getConnection(); + if (remoteAgentConnection) { + return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); + } + return null; + } } - - const remoteAgentConnection = this._remoteAgentService.getConnection(); - if (remoteAgentConnection) { - const remoteExtHost = this._instantiationService.createInstance(RemoteExtensionHost, this._createRemoteExtensionHostDataProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); - result.push(remoteExtHost); - } - - return result; } protected override _onExtensionHostCrashed(extensionHost: IExtensionHostManager, code: number, signal: string | null): void { @@ -340,18 +344,88 @@ export class ExtensionService extends AbstractExtensionService implements IExten // --- impl + private async _resolveAuthority(remoteAuthority: string): Promise { + + const authorityPlusIndex = remoteAuthority.indexOf('+'); + if (authorityPlusIndex === -1) { + // This authority does not need to be resolved, simply parse the port number + const lastColon = remoteAuthority.lastIndexOf(':'); + return { + authority: { + authority: remoteAuthority, + host: remoteAuthority.substring(0, lastColon), + port: parseInt(remoteAuthority.substring(lastColon + 1), 10), + connectionToken: undefined + } + }; + } + + const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess); + if (localProcessExtensionHosts.length === 0) { + // no local process extension hosts + throw new Error(`Cannot resolve authority`); + } + + this._resolveAuthorityAttempt++; + const results = await Promise.all(localProcessExtensionHosts.map(extHost => extHost.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt))); + + let bestErrorResult: IResolveAuthorityErrorResult | null = null; + for (const result of results) { + if (result.type === 'ok') { + return result.value; + } + if (!bestErrorResult) { + bestErrorResult = result; + continue; + } + const bestErrorIsUnknown = (bestErrorResult.error.code === RemoteAuthorityResolverErrorCode.Unknown); + const errorIsUnknown = (result.error.code === RemoteAuthorityResolverErrorCode.Unknown); + if (bestErrorIsUnknown && !errorIsUnknown) { + bestErrorResult = result; + } + } + + // we can only reach this if there is an error + throw new RemoteAuthorityResolverError(bestErrorResult!.error.message, bestErrorResult!.error.code, bestErrorResult!.error.detail); + } + + private async _getCanonicalURI(remoteAuthority: string, uri: URI): Promise { + + const authorityPlusIndex = remoteAuthority.indexOf('+'); + if (authorityPlusIndex === -1) { + // This authority does not use a resolver + return uri; + } + + const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess); + if (localProcessExtensionHosts.length === 0) { + // no local process extension hosts + throw new Error(`Cannot resolve canonical URI`); + } + + const results = await Promise.all(localProcessExtensionHosts.map(extHost => extHost.getCanonicalURI(remoteAuthority, uri))); + + for (const result of results) { + if (result) { + return result; + } + } + + // we can only reach this if there was no resolver extension that can return the cannonical uri + throw new Error(`Cannot get canonical URI because no extension is installed to resolve ${getRemoteAuthorityPrefix(remoteAuthority)}`); + } + private async _resolveAuthorityAgain(): Promise { const remoteAuthority = this._environmentService.remoteAuthority; if (!remoteAuthority) { return; } - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!; this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority); const sw = StopWatch.create(false); this._logService.info(`Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`); try { - const result = await localProcessExtensionHost.resolveAuthority(remoteAuthority); + const result = await this._resolveAuthority(remoteAuthority); this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed()} ms`); this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options); } catch (err) { @@ -364,7 +438,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._extensionScanner.startScanningExtensions(this.createLogger()); const remoteAuthority = this._environmentService.remoteAuthority; - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!; let remoteEnv: IRemoteAgentEnvironment | null = null; let remoteExtensions: IExtensionDescription[] = []; @@ -376,12 +449,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten // The current remote authority resolver cannot give the canonical URI for this URI return uri; } - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!; if (isCI) { this._logService.info(`Invoking getCanonicalURI for authority ${getRemoteAuthorityPrefix(remoteAuthority)}...`); } try { - return localProcessExtensionHost.getCanonicalURI(remoteAuthority, uri); + return this._getCanonicalURI(remoteAuthority, uri); } finally { if (isCI) { this._logService.info(`getCanonicalURI returned for authority ${getRemoteAuthorityPrefix(remoteAuthority)}.`); @@ -407,7 +479,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const sw = StopWatch.create(false); this._logService.info(`Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`); try { - resolverResult = await localProcessExtensionHost.resolveAuthority(remoteAuthority); + resolverResult = await this._resolveAuthority(remoteAuthority); this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${resolverResult.authority.host}:${resolverResult.authority.port}' after ${sw.elapsed()} ms`); } catch (err) { this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err); @@ -471,12 +543,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions, false); const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions(), false); - this._runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, remoteExtensions); + this._initializeRunningLocation(localExtensions, remoteExtensions); // remove non-UI extensions from the local extensions - const localProcessExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalProcess); - const localWebWorkerExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker); - remoteExtensions = filterByRunningLocation(remoteExtensions, this._runningLocation, ExtensionRunningLocation.Remote); + const localProcessExtensions = this._filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalProcess); + const localWebWorkerExtensions = this._filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalWebWorker); + remoteExtensions = this._filterByExtensionHostKind(remoteExtensions, ExtensionHostKind.Remote); const result = this._registry.deltaExtensions(remoteExtensions.concat(localProcessExtensions).concat(localWebWorkerExtensions), []); if (result.removedDueToLooping.length > 0) { @@ -498,23 +570,22 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess); - if (localProcessExtensionHost) { - localProcessExtensionHost.start(localProcessExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); + const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess); + const filteredLocalProcessExtensions = localProcessExtensions.filter(extension => this._registry.containsExtension(extension.identifier)); + for (const extHost of localProcessExtensionHosts) { + this._startExtensionHost(extHost, filteredLocalProcessExtensions); } - const localWebWorkerExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalWebWorker); - if (localWebWorkerExtensionHost) { - localWebWorkerExtensionHost.start(localWebWorkerExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); + const localWebWorkerExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalWebWorker); + const filteredLocalWebWorkerExtensions = localWebWorkerExtensions.filter(extension => this._registry.containsExtension(extension.identifier)); + for (const extHost of localWebWorkerExtensionHosts) { + this._startExtensionHost(extHost, filteredLocalWebWorkerExtensions); } } - public override async getInspectPort(tryEnableInspector: boolean): Promise { - const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess); - if (localProcessExtensionHost) { - return localProcessExtensionHost.getInspectPort(tryEnableInspector); - } - return 0; + private _startExtensionHost(extensionHostManager: IExtensionHostManager, _extensions: IExtensionDescription[]): void { + const extensions = this._filterByExtensionHostManager(_extensions, extensionHostManager); + extensionHostManager.start(extensions.map(extension => extension.identifier)); } public _onExtensionHostExit(code: number): void { @@ -630,10 +701,6 @@ function getRemoteAuthorityPrefix(remoteAuthority: string): string { return remoteAuthority.substring(0, plusIndex); } -function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] { - return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === desiredRunningLocation); -} - registerSingleton(IExtensionService, ExtensionService); class RestartExtensionHostAction extends Action2 { diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 79556450046..2f7e9b944f6 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -34,7 +34,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { parseExtensionDevOptions } from '../common/extensionDevOptions'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; -import { IExtensionHost, ExtensionHostLogFileName, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionHost, ExtensionHostLogFileName, LocalProcessRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -44,6 +44,7 @@ import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform import { SerializedError } from 'vs/base/common/errors'; import { removeDangerousEnvVariables } from 'vs/base/node/processes'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; export interface ILocalProcessExtensionHostInitData { readonly autoStart: boolean; @@ -105,9 +106,9 @@ class ExtensionHostProcess { export class LocalProcessExtensionHost implements IExtensionHost { - public readonly kind = ExtensionHostKind.LocalProcess; public readonly remoteAuthority = null; public readonly lazyStart = false; + public readonly extensions = new ExtensionDescriptionRegistry([]); private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>(); public readonly onExit: Event<[number, string]> = this._onExit.event; @@ -135,6 +136,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { private readonly _extensionHostLogFile: URI; constructor( + public readonly runningLocation: LocalProcessRunningLocation, private readonly _initDataProvider: ILocalProcessExtensionHostDataProvider, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @INotificationService private readonly _notificationService: INotificationService, @@ -502,6 +504,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { private async _createExtHostInitData(): Promise { const [telemetryInfo, initData] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]); const workspace = this._contextService.getWorkspace(); + this.extensions.deltaExtensions(initData.extensions, []); return { commit: this._productService.commit, version: this._productService.version, @@ -532,7 +535,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { }, resolvedExtensions: [], hostExtensions: [], - extensions: initData.extensions, + extensions: this.extensions.getAllExtensionDescriptions(), telemetryInfo, logLevel: this._logService.getLevel(), logsLocation: this._environmentService.extHostLogsPath, diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index ec46f3b43be..ecc2877518c 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -6,84 +6,84 @@ import * as assert from 'assert'; import { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService'; import { ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/abstractExtensionService'; -import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; suite('BrowserExtensionService', () => { test('pickRunningLocation', () => { - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.None); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, true, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, true, ExtensionRunningPreference.None), null); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false, ExtensionRunningPreference.None), ExtensionRunningLocation.None); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionRunningLocation.LocalWebWorker); - assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionRunningLocation.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false, ExtensionRunningPreference.None), null); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false, ExtensionRunningPreference.None), ExtensionHostKind.LocalWebWorker); + assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); }); });