Path: blob/main/src/vs/workbench/services/extensions/common/extensionRunningLocationTracker.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Schemas } from '../../../../base/common/network.js';6import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';7import { ExtensionKind } from '../../../../platform/environment/common/environment.js';8import { ExtensionIdentifier, ExtensionIdentifierMap, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';9import { ILogService } from '../../../../platform/log/common/log.js';10import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';11import { IReadOnlyExtensionDescriptionRegistry } from './extensionDescriptionRegistry.js';12import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, determineExtensionHostKinds } from './extensionHostKind.js';13import { IExtensionHostManager } from './extensionHostManagers.js';14import { IExtensionManifestPropertiesService } from './extensionManifestPropertiesService.js';15import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, RemoteRunningLocation } from './extensionRunningLocation.js';1617export class ExtensionRunningLocationTracker {1819private _runningLocation = new ExtensionIdentifierMap<ExtensionRunningLocation | null>();20private _maxLocalProcessAffinity: number = 0;21private _maxLocalWebWorkerAffinity: number = 0;2223public get maxLocalProcessAffinity(): number {24return this._maxLocalProcessAffinity;25}2627public get maxLocalWebWorkerAffinity(): number {28return this._maxLocalWebWorkerAffinity;29}3031constructor(32private readonly _registry: IReadOnlyExtensionDescriptionRegistry,33private readonly _extensionHostKindPicker: IExtensionHostKindPicker,34@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,35@IConfigurationService private readonly _configurationService: IConfigurationService,36@ILogService private readonly _logService: ILogService,37@IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,38) { }3940public set(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation) {41this._runningLocation.set(extensionId, runningLocation);42}4344public readExtensionKinds(extensionDescription: IExtensionDescription): ExtensionKind[] {45if (extensionDescription.isUnderDevelopment && this._environmentService.extensionDevelopmentKind) {46return this._environmentService.extensionDevelopmentKind;47}4849return this._extensionManifestPropertiesService.getExtensionKind(extensionDescription);50}5152public getRunningLocation(extensionId: ExtensionIdentifier): ExtensionRunningLocation | null {53return this._runningLocation.get(extensionId) || null;54}5556public filterByRunningLocation(extensions: readonly IExtensionDescription[], desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] {57return filterExtensionDescriptions(extensions, this._runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation));58}5960public filterByExtensionHostKind(extensions: readonly IExtensionDescription[], desiredExtensionHostKind: ExtensionHostKind): IExtensionDescription[] {61return filterExtensionDescriptions(extensions, this._runningLocation, extRunningLocation => extRunningLocation.kind === desiredExtensionHostKind);62}6364public filterByExtensionHostManager(extensions: readonly IExtensionDescription[], extensionHostManager: IExtensionHostManager): IExtensionDescription[] {65return filterExtensionDescriptions(extensions, this._runningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation));66}6768private _computeAffinity(inputExtensions: IExtensionDescription[], extensionHostKind: ExtensionHostKind, isInitialAllocation: boolean): { affinities: ExtensionIdentifierMap<number>; maxAffinity: number } {69// Only analyze extensions that can execute70const extensions = new ExtensionIdentifierMap<IExtensionDescription>();71for (const extension of inputExtensions) {72if (extension.main || extension.browser) {73extensions.set(extension.identifier, extension);74}75}76// Also add existing extensions of the same kind that can execute77for (const extension of this._registry.getAllExtensionDescriptions()) {78if (extension.main || extension.browser) {79const runningLocation = this._runningLocation.get(extension.identifier);80if (runningLocation && runningLocation.kind === extensionHostKind) {81extensions.set(extension.identifier, extension);82}83}84}8586// Initially, each extension belongs to its own group87const groups = new ExtensionIdentifierMap<number>();88let groupNumber = 0;89for (const [_, extension] of extensions) {90groups.set(extension.identifier, ++groupNumber);91}9293const changeGroup = (from: number, to: number) => {94for (const [key, group] of groups) {95if (group === from) {96groups.set(key, to);97}98}99};100101// We will group things together when there are dependencies102for (const [_, extension] of extensions) {103if (!extension.extensionDependencies) {104continue;105}106const myGroup = groups.get(extension.identifier)!;107for (const depId of extension.extensionDependencies) {108const depGroup = groups.get(depId);109if (!depGroup) {110// probably can't execute, so it has no impact111continue;112}113114if (depGroup === myGroup) {115// already in the same group116continue;117}118119changeGroup(depGroup, myGroup);120}121}122123// Initialize with existing affinities124const resultingAffinities = new Map<number, number>();125let lastAffinity = 0;126for (const [_, extension] of extensions) {127const runningLocation = this._runningLocation.get(extension.identifier);128if (runningLocation) {129const group = groups.get(extension.identifier)!;130resultingAffinities.set(group, runningLocation.affinity);131lastAffinity = Math.max(lastAffinity, runningLocation.affinity);132}133}134135// When doing extension host debugging, we will ignore the configured affinity136// because we can currently debug a single extension host137if (!this._environmentService.isExtensionDevelopment) {138// Go through each configured affinity and try to accomodate it139const configuredAffinities = this._configurationService.getValue<{ [extensionId: string]: number } | undefined>('extensions.experimental.affinity') || {};140const configuredExtensionIds = Object.keys(configuredAffinities);141const configuredAffinityToResultingAffinity = new Map<number, number>();142for (const extensionId of configuredExtensionIds) {143const configuredAffinity = configuredAffinities[extensionId];144if (typeof configuredAffinity !== 'number' || configuredAffinity <= 0 || Math.floor(configuredAffinity) !== configuredAffinity) {145this._logService.info(`Ignoring configured affinity for '${extensionId}' because the value is not a positive integer.`);146continue;147}148const group = groups.get(extensionId);149if (!group) {150// The extension is not known or cannot execute for this extension host kind151continue;152}153154const affinity1 = resultingAffinities.get(group);155if (affinity1) {156// Affinity for this group is already established157configuredAffinityToResultingAffinity.set(configuredAffinity, affinity1);158continue;159}160161const affinity2 = configuredAffinityToResultingAffinity.get(configuredAffinity);162if (affinity2) {163// Affinity for this configuration is already established164resultingAffinities.set(group, affinity2);165continue;166}167168if (!isInitialAllocation) {169this._logService.info(`Ignoring configured affinity for '${extensionId}' because extension host(s) are already running. Reload window.`);170continue;171}172173const affinity3 = ++lastAffinity;174configuredAffinityToResultingAffinity.set(configuredAffinity, affinity3);175resultingAffinities.set(group, affinity3);176}177}178179const result = new ExtensionIdentifierMap<number>();180for (const extension of inputExtensions) {181const group = groups.get(extension.identifier) || 0;182const affinity = resultingAffinities.get(group) || 0;183result.set(extension.identifier, affinity);184}185186if (lastAffinity > 0 && isInitialAllocation) {187for (let affinity = 1; affinity <= lastAffinity; affinity++) {188const extensionIds: ExtensionIdentifier[] = [];189for (const extension of inputExtensions) {190if (result.get(extension.identifier) === affinity) {191extensionIds.push(extension.identifier);192}193}194this._logService.info(`Placing extension(s) ${extensionIds.map(e => e.value).join(', ')} on a separate extension host.`);195}196}197198return { affinities: result, maxAffinity: lastAffinity };199}200201public computeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): ExtensionIdentifierMap<ExtensionRunningLocation | null> {202return this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, isInitialAllocation).runningLocation;203}204205private _doComputeRunningLocation(existingRunningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>; maxLocalProcessAffinity: number; maxLocalWebWorkerAffinity: number } {206// Skip extensions that have an existing running location207localExtensions = localExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));208remoteExtensions = remoteExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));209210const extensionHostKinds = determineExtensionHostKinds(211localExtensions,212remoteExtensions,213(extension) => this.readExtensionKinds(extension),214(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._extensionHostKindPicker.pickExtensionHostKind(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference)215);216217const extensions = new ExtensionIdentifierMap<IExtensionDescription>();218for (const extension of localExtensions) {219extensions.set(extension.identifier, extension);220}221for (const extension of remoteExtensions) {222extensions.set(extension.identifier, extension);223}224225const result = new ExtensionIdentifierMap<ExtensionRunningLocation | null>();226const localProcessExtensions: IExtensionDescription[] = [];227const localWebWorkerExtensions: IExtensionDescription[] = [];228for (const [extensionIdKey, extensionHostKind] of extensionHostKinds) {229let runningLocation: ExtensionRunningLocation | null = null;230if (extensionHostKind === ExtensionHostKind.LocalProcess) {231const extensionDescription = extensions.get(extensionIdKey);232if (extensionDescription) {233localProcessExtensions.push(extensionDescription);234}235} else if (extensionHostKind === ExtensionHostKind.LocalWebWorker) {236const extensionDescription = extensions.get(extensionIdKey);237if (extensionDescription) {238localWebWorkerExtensions.push(extensionDescription);239}240} else if (extensionHostKind === ExtensionHostKind.Remote) {241runningLocation = new RemoteRunningLocation();242}243result.set(extensionIdKey, runningLocation);244}245246const { affinities, maxAffinity } = this._computeAffinity(localProcessExtensions, ExtensionHostKind.LocalProcess, isInitialAllocation);247for (const extension of localProcessExtensions) {248const affinity = affinities.get(extension.identifier) || 0;249result.set(extension.identifier, new LocalProcessRunningLocation(affinity));250}251const { affinities: localWebWorkerAffinities, maxAffinity: maxLocalWebWorkerAffinity } = this._computeAffinity(localWebWorkerExtensions, ExtensionHostKind.LocalWebWorker, isInitialAllocation);252for (const extension of localWebWorkerExtensions) {253const affinity = localWebWorkerAffinities.get(extension.identifier) || 0;254result.set(extension.identifier, new LocalWebWorkerRunningLocation(affinity));255}256257// Add extensions that already have an existing running location258for (const [extensionIdKey, runningLocation] of existingRunningLocation) {259if (runningLocation) {260result.set(extensionIdKey, runningLocation);261}262}263264return { runningLocation: result, maxLocalProcessAffinity: maxAffinity, maxLocalWebWorkerAffinity: maxLocalWebWorkerAffinity };265}266267public initializeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[]): void {268const { runningLocation, maxLocalProcessAffinity, maxLocalWebWorkerAffinity } = this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, true);269this._runningLocation = runningLocation;270this._maxLocalProcessAffinity = maxLocalProcessAffinity;271this._maxLocalWebWorkerAffinity = maxLocalWebWorkerAffinity;272}273274/**275* Returns the running locations for the removed extensions.276*/277public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): ExtensionIdentifierMap<ExtensionRunningLocation | null> {278// Remove old running location279const removedRunningLocation = new ExtensionIdentifierMap<ExtensionRunningLocation | null>();280for (const extensionId of toRemove) {281const extensionKey = extensionId;282removedRunningLocation.set(extensionKey, this._runningLocation.get(extensionKey) || null);283this._runningLocation.delete(extensionKey);284}285286// Determine new running location287this._updateRunningLocationForAddedExtensions(toAdd);288289return removedRunningLocation;290}291292/**293* Update `this._runningLocation` with running locations for newly enabled/installed extensions.294*/295private _updateRunningLocationForAddedExtensions(toAdd: IExtensionDescription[]): void {296// Determine new running location297const localProcessExtensions: IExtensionDescription[] = [];298const localWebWorkerExtensions: IExtensionDescription[] = [];299for (const extension of toAdd) {300const extensionKind = this.readExtensionKinds(extension);301const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;302const extensionHostKind = this._extensionHostKindPicker.pickExtensionHostKind(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None);303let runningLocation: ExtensionRunningLocation | null = null;304if (extensionHostKind === ExtensionHostKind.LocalProcess) {305localProcessExtensions.push(extension);306} else if (extensionHostKind === ExtensionHostKind.LocalWebWorker) {307localWebWorkerExtensions.push(extension);308} else if (extensionHostKind === ExtensionHostKind.Remote) {309runningLocation = new RemoteRunningLocation();310}311this._runningLocation.set(extension.identifier, runningLocation);312}313314const { affinities } = this._computeAffinity(localProcessExtensions, ExtensionHostKind.LocalProcess, false);315for (const extension of localProcessExtensions) {316const affinity = affinities.get(extension.identifier) || 0;317this._runningLocation.set(extension.identifier, new LocalProcessRunningLocation(affinity));318}319320const { affinities: webWorkerExtensionsAffinities } = this._computeAffinity(localWebWorkerExtensions, ExtensionHostKind.LocalWebWorker, false);321for (const extension of localWebWorkerExtensions) {322const affinity = webWorkerExtensionsAffinities.get(extension.identifier) || 0;323this._runningLocation.set(extension.identifier, new LocalWebWorkerRunningLocation(affinity));324}325}326}327328export function filterExtensionDescriptions(extensions: readonly IExtensionDescription[], runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, predicate: (extRunningLocation: ExtensionRunningLocation) => boolean): IExtensionDescription[] {329return extensions.filter((ext) => {330const extRunningLocation = runningLocation.get(ext.identifier);331return extRunningLocation && predicate(extRunningLocation);332});333}334335export function filterExtensionIdentifiers(extensions: readonly ExtensionIdentifier[], runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, predicate: (extRunningLocation: ExtensionRunningLocation) => boolean): ExtensionIdentifier[] {336return extensions.filter((ext) => {337const extRunningLocation = runningLocation.get(ext);338return extRunningLocation && predicate(extRunningLocation);339});340}341342343