src/vs/workbench/services/extensions/common/extensionRunningLocationTracker.ts

368 LOC · 252 covered · 116 uncovered · 36 ranges · 5 concepts · 5 introducers · 6 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/services/extensions/common/extensionHostKind.ts · 148 LOCcommon/extensionHostKind…src/vs/workbench/services/extensions/common/extensionRunningLocation.ts · 51 LOCcommon/extensionRunningL…extensionRunningLocationTracker.ts ×5 · 34 introduced LOCextensionRunningLocation…extensionRunningLocationTracker.ts ×3 · 10 introduced LOCextensionRunningLocation…extensionRunningLocationTracker.ts ×3 · 11 introduced LOCextensionRunningLocation…extensionRunningLocationTracker.ts ×1 · 3 introduced LOCextensionRunningLocation…extensionRunningLocationTracker.ts ×24 · 295 introduced LOCextensionRunningLocation…extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity extensionAffinity combined with extensionDependencies|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/extensions/test/common/extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity extensionAffinity combined with extensionDependencies|occurrence=1extensionRunningLocation…extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity extensionAffinity with non-installed extension should be ignored|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/extensions/test/common/extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity extensionAffinity with non-installed extension should be ignored|occurrence=1extensionRunningLocation…extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity extensions with extensionAffinity should have the same affinity|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/extensions/test/common/extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity extensions with extensionAffinity should have the same affinity|occurrence=1extensionRunningLocation…extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity one-way extensionAffinity is sufficient|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/extensions/test/common/extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity one-way extensionAffinity is sufficient|occurrence=1extensionRunningLocation…extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity transitive extensionAffinity should group all extensions together|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/extensions/test/common/extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity transitive extensionAffinity should group all extensions together|occurrence=1extensionRunningLocation…extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity user configured affinity should override extensionAffinity|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/extensions/test/common/extensionRunningLocationTracker.test|title=ExtensionRunningLocationTracker - extensionAffinity user configured affinity should override extensionAffinity|occurrence=1extensionRunningLocation…Focused file · src/vs/workbench/services/extensions/common/extensionRunningLocationTracker.ts · 368 LOCcommon/extensionRunningL…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extensionRunningLocationTracker.ts ×24
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Schemas } from '../../../../base/common/network.js';
7 > import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
8 > import { ExtensionKind } from '../../../../platform/environment/common/environment.js';
9 > import { ExtensionIdentifier, ExtensionIdentifierMap, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
10 > import { ILogService } from '../../../../platform/log/common/log.js';
11 > import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
12 > import { IReadOnlyExtensionDescriptionRegistry } from './extensionDescriptionRegistry.js';
13 > import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, determineExtensionHostKinds } from './extensionHostKind.js';
14 > import { IExtensionHostManager } from './extensionHostManagers.js';
15 > import { IExtensionManifestPropertiesService } from './extensionManifestPropertiesService.js';
16 > import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, RemoteRunningLocation } from './extensionRunningLocation.js';
17 > import { isProposedApiEnabled } from './extensions.js';
18 >
19 > export class ExtensionRunningLocationTracker {
20 >
21 > private _runningLocation = new ExtensionIdentifierMap<ExtensionRunningLocation | null>();
22 > private _maxLocalProcessAffinity: number = 0;
23 > private _maxLocalWebWorkerAffinity: number = 0;
24 >
25 > public get maxLocalProcessAffinity(): number {
26 > return this._maxLocalProcessAffinity;
27 > }
28 >
29 > public get maxLocalWebWorkerAffinity(): number {
30 return this._maxLocalWebWorkerAffinity;
31 }
33 > constructor(
34 > private readonly _registry: IReadOnlyExtensionDescriptionRegistry,
35 > private readonly _extensionHostKindPicker: IExtensionHostKindPicker,
36 > @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
37 > @IConfigurationService private readonly _configurationService: IConfigurationService,
38 > @ILogService private readonly _logService: ILogService,
39 > @IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
40 > ) { }
41 >
42 > public set(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation) {
43 this._runningLocation.set(extensionId, runningLocation);
44 }
46 > public readExtensionKinds(extensionDescription: IExtensionDescription): ExtensionKind[] {
47 > if (extensionDescription.isUnderDevelopment && this._environmentService.extensionDevelopmentKind) {
48 return this._environmentService.extensionDevelopmentKind;
49 }
51 > return this._extensionManifestPropertiesService.getExtensionKind(extensionDescription);
52 > }
53 >
54 > public getRunningLocation(extensionId: ExtensionIdentifier): ExtensionRunningLocation | null {
55 return this._runningLocation.get(extensionId) || null;
56 }
58 > public filterByRunningLocation(extensions: readonly IExtensionDescription[], desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] {
59 return filterExtensionDescriptions(extensions, this._runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation));
60 }
62 > public filterByExtensionHostKind(extensions: readonly IExtensionDescription[], desiredExtensionHostKind: ExtensionHostKind): IExtensionDescription[] {
63 return filterExtensionDescriptions(extensions, this._runningLocation, extRunningLocation => extRunningLocation.kind === desiredExtensionHostKind);
64 }
66 > public filterByExtensionHostManager(extensions: readonly IExtensionDescription[], extensionHostManager: IExtensionHostManager): IExtensionDescription[] {
67 return filterExtensionDescriptions(extensions, this._runningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation));
68 }
70 > private _computeAffinity(inputExtensions: IExtensionDescription[], extensionHostKind: ExtensionHostKind, isInitialAllocation: boolean): { affinities: ExtensionIdentifierMap<number>; maxAffinity: number } {
71 > // Only analyze extensions that can execute
72 > const extensions = new ExtensionIdentifierMap<IExtensionDescription>();
73 > for (const extension of inputExtensions) {
74 > if (extension.main || extension.browser) {
75 > extensions.set(extension.identifier, extension);
76 > }
77 > }
78 > // Also add existing extensions of the same kind that can execute
79 > for (const extension of this._registry.getAllExtensionDescriptions()) {
80 > if (extension.main || extension.browser) {
81 > const runningLocation = this._runningLocation.get(extension.identifier);
82 > if (runningLocation && runningLocation.kind === extensionHostKind) {
83 extensions.set(extension.identifier, extension);
84 }
86 > }
87 >
88 > // Initially, each extension belongs to its own group
89 > const groups = new ExtensionIdentifierMap<number>();
90 > let groupNumber = 0;
91 > for (const [_, extension] of extensions) {
92 > groups.set(extension.identifier, ++groupNumber);
93 > }
94 >
95 > const changeGroup = (from: number, to: number) => {
96 > for (const [key, group] of groups) { extensionRunningLocationTracker.ts ×3
97 > if (group === from) {
98 > groups.set(key, to);
99 > }
100 > }
101 > };
103 > // We will group things together when there are dependencies
104 > for (const [_, extension] of extensions) {
105 > if (!extension.extensionDependencies) {
106 > continue;
107 > }
108 > const myGroup = groups.get(extension.identifier)!; extensionRunningLocationTracker.ts ×3
109 > for (const depId of extension.extensionDependencies) {
110 > const depGroup = groups.get(depId);
111 > if (!depGroup) {
112 // probably can't execute, so it has no impact
113 continue;
114 }
116 > if (depGroup === myGroup) {
117 // already in the same group
118 continue;
119 }
121 > changeGroup(depGroup, myGroup);
122 > }
123 > }
125 > // We will also group things together when there are extensionAffinity declarations
126 > for (const [_, extension] of extensions) {
127 > if (!extension.extensionAffinity) {
128 > continue;
129 > }
130 > if (!isProposedApiEnabled(extension, 'extensionAffinity')) {
131 this._logService.warn(`Extension '${extension.identifier.value}' declares 'extensionAffinity' in its package.json but does not enable the 'extensionAffinity' API proposal. Add '"enabledApiProposals": ["extensionAffinity"]' to the extension's package.json to use this feature.`);
132 continue;
133 }
134 > const myGroup = groups.get(extension.identifier)!; extensionRunningLocationTracker.ts ×24
135 > for (const colocateId of extension.extensionAffinity) {
136 > const colocateGroup = groups.get(colocateId);
137 > if (!colocateGroup) {
138 > // the extension is not installed or can't execute, so it has no impact extensionRunningLocationTracker.ts ×1
139 > continue;
140 > }
142 > if (colocateGroup === myGroup) {
143 // already in the same group
144 continue;
145 }
147 > changeGroup(colocateGroup, myGroup);
148 > }
150 >
151 > // Initialize with existing affinities
152 > const resultingAffinities = new Map<number, number>();
153 > let lastAffinity = 0;
154 > for (const [_, extension] of extensions) {
155 > const runningLocation = this._runningLocation.get(extension.identifier);
156 > if (runningLocation) {
157 const group = groups.get(extension.identifier)!;
158 resultingAffinities.set(group, runningLocation.affinity);
159 lastAffinity = Math.max(lastAffinity, runningLocation.affinity);
160 }
162 >
163 > // When doing extension host debugging, we will ignore the configured affinity
164 > // because we can currently debug a single extension host
165 > if (!this._environmentService.isExtensionDevelopment) {
166 > // Go through each configured affinity and try to accomodate it
167 > const configuredAffinities = this._configurationService.getValue<{ [extensionId: string]: number } | undefined>('extensions.experimental.affinity') || {};
168 > const configuredExtensionIds = Object.keys(configuredAffinities);
169 > const configuredAffinityToResultingAffinity = new Map<number, number>();
170 > for (const extensionId of configuredExtensionIds) {
171 > const configuredAffinity = configuredAffinities[extensionId]; extensionRunningLocationTracker.ts ×5
172 > if (typeof configuredAffinity !== 'number' || configuredAffinity <= 0 || Math.floor(configuredAffinity) !== configuredAffinity) {
173 this._logService.info(`Ignoring configured affinity for '${extensionId}' because the value is not a positive integer.`);
174 continue;
175 }
176 > const group = groups.get(extensionId); extensionRunningLocationTracker.ts ×5
177 > if (!group) {
178 > // The extension is not known or cannot execute for this extension host kind
179 > continue;
180 > }
181 >
182 > const affinity1 = resultingAffinities.get(group);
183 > if (affinity1) {
184 > // Affinity for this group is already established
185 > configuredAffinityToResultingAffinity.set(configuredAffinity, affinity1);
186 > continue;
187 > }
188 >
189 > const affinity2 = configuredAffinityToResultingAffinity.get(configuredAffinity);
190 > if (affinity2) {
191 // Affinity for this configuration is already established
192 resultingAffinities.set(group, affinity2);
193 continue;
194 }
196 > if (!isInitialAllocation) {
197 this._logService.info(`Ignoring configured affinity for '${extensionId}' because extension host(s) are already running. Reload window.`);
198 continue;
199 }
201 > const affinity3 = ++lastAffinity;
202 > configuredAffinityToResultingAffinity.set(configuredAffinity, affinity3);
203 > resultingAffinities.set(group, affinity3);
204 > }
206 >
207 > const result = new ExtensionIdentifierMap<number>();
208 > for (const extension of inputExtensions) {
209 > const group = groups.get(extension.identifier) || 0;
210 > const affinity = resultingAffinities.get(group) || 0;
211 > result.set(extension.identifier, affinity);
212 > }
213 >
214 > if (lastAffinity > 0 && isInitialAllocation) {
215 > for (let affinity = 1; affinity <= lastAffinity; affinity++) { extensionRunningLocationTracker.ts ×5
216 > const extensionIds: ExtensionIdentifier[] = [];
217 > for (const extension of inputExtensions) {
218 > if (result.get(extension.identifier) === affinity) {
219 > extensionIds.push(extension.identifier);
220 > }
221 > }
222 > this._logService.info(`Placing extension(s) ${extensionIds.map(e => e.value).join(', ')} on a separate extension host.`);
223 > }
224 > }
226 > return { affinities: result, maxAffinity: lastAffinity };
227 > }
228 >
229 > public computeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): ExtensionIdentifierMap<ExtensionRunningLocation | null> {
230 > return this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, isInitialAllocation).runningLocation;
231 > }
232 >
233 > private _doComputeRunningLocation(existingRunningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], isInitialAllocation: boolean): { runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>; maxLocalProcessAffinity: number; maxLocalWebWorkerAffinity: number } {
234 > // Skip extensions that have an existing running location
235 > localExtensions = localExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));
236 > remoteExtensions = remoteExtensions.filter(extension => !existingRunningLocation.has(extension.identifier));
237 >
238 > const extensionHostKinds = determineExtensionHostKinds(
239 > localExtensions,
240 > remoteExtensions,
241 > (extension) => this.readExtensionKinds(extension),
242 > (extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._extensionHostKindPicker.pickExtensionHostKind(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference)
243 > );
244 >
245 > const extensions = new ExtensionIdentifierMap<IExtensionDescription>();
246 > for (const extension of localExtensions) {
247 > extensions.set(extension.identifier, extension);
248 > }
249 > for (const extension of remoteExtensions) {
250 extensions.set(extension.identifier, extension);
251 }
253 > const result = new ExtensionIdentifierMap<ExtensionRunningLocation | null>();
254 > const localProcessExtensions: IExtensionDescription[] = [];
255 > const localWebWorkerExtensions: IExtensionDescription[] = [];
256 > for (const [extensionIdKey, extensionHostKind] of extensionHostKinds) {
257 > let runningLocation: ExtensionRunningLocation | null = null;
258 > if (extensionHostKind === ExtensionHostKind.LocalProcess) {
259 > const extensionDescription = extensions.get(extensionIdKey);
260 > if (extensionDescription) {
261 > localProcessExtensions.push(extensionDescription);
262 > }
263 > } else if (extensionHostKind === ExtensionHostKind.LocalWebWorker) {
264 const extensionDescription = extensions.get(extensionIdKey);
265 if (extensionDescription) {
266 localWebWorkerExtensions.push(extensionDescription);
267 }
268 } else if (extensionHostKind === ExtensionHostKind.Remote) {
269 runningLocation = new RemoteRunningLocation();
270 }
271 > result.set(extensionIdKey, runningLocation); extensionRunningLocationTracker.ts ×24
272 > }
273 >
274 > const { affinities, maxAffinity } = this._computeAffinity(localProcessExtensions, ExtensionHostKind.LocalProcess, isInitialAllocation);
275 > for (const extension of localProcessExtensions) {
276 > const affinity = affinities.get(extension.identifier) || 0;
277 > result.set(extension.identifier, new LocalProcessRunningLocation(affinity));
278 > }
279 > const { affinities: localWebWorkerAffinities, maxAffinity: maxLocalWebWorkerAffinity } = this._computeAffinity(localWebWorkerExtensions, ExtensionHostKind.LocalWebWorker, isInitialAllocation);
280 > for (const extension of localWebWorkerExtensions) {
281 const affinity = localWebWorkerAffinities.get(extension.identifier) || 0;
282 result.set(extension.identifier, new LocalWebWorkerRunningLocation(affinity));
283 }
285 > // Add extensions that already have an existing running location
286 > for (const [extensionIdKey, runningLocation] of existingRunningLocation) {
287 if (runningLocation) {
288 result.set(extensionIdKey, runningLocation);
289 }
290 }
292 > return { runningLocation: result, maxLocalProcessAffinity: maxAffinity, maxLocalWebWorkerAffinity: maxLocalWebWorkerAffinity };
293 > }
294 >
295 > public initializeRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[]): void {
296 const { runningLocation, maxLocalProcessAffinity, maxLocalWebWorkerAffinity } = this._doComputeRunningLocation(this._runningLocation, localExtensions, remoteExtensions, true);
297 this._runningLocation = runningLocation;
298 this._maxLocalProcessAffinity = maxLocalProcessAffinity;
299 this._maxLocalWebWorkerAffinity = maxLocalWebWorkerAffinity;
300 }
302 > /**
303 > * Returns the running locations for the removed extensions.
304 > */
305 > public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): ExtensionIdentifierMap<ExtensionRunningLocation | null> {
306 // Remove old running location
307 const removedRunningLocation = new ExtensionIdentifierMap<ExtensionRunningLocation | null>();
308 for (const extensionId of toRemove) {
309 const extensionKey = extensionId;
310 removedRunningLocation.set(extensionKey, this._runningLocation.get(extensionKey) || null);
311 this._runningLocation.delete(extensionKey);
312 }
313
314 // Determine new running location
315 this._updateRunningLocationForAddedExtensions(toAdd);
316
317 return removedRunningLocation;
318 }
320 > /**
321 > * Update `this._runningLocation` with running locations for newly enabled/installed extensions.
322 > */
323 > private _updateRunningLocationForAddedExtensions(toAdd: IExtensionDescription[]): void {
324 // Determine new running location
325 const localProcessExtensions: IExtensionDescription[] = [];
326 const localWebWorkerExtensions: IExtensionDescription[] = [];
327 for (const extension of toAdd) {
328 const extensionKind = this.readExtensionKinds(extension);
329 const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;
330 const extensionHostKind = this._extensionHostKindPicker.pickExtensionHostKind(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None);
331 let runningLocation: ExtensionRunningLocation | null = null;
332 if (extensionHostKind === ExtensionHostKind.LocalProcess) {
333 localProcessExtensions.push(extension);
334 } else if (extensionHostKind === ExtensionHostKind.LocalWebWorker) {
335 localWebWorkerExtensions.push(extension);
336 } else if (extensionHostKind === ExtensionHostKind.Remote) {
337 runningLocation = new RemoteRunningLocation();
338 }
339 this._runningLocation.set(extension.identifier, runningLocation);
340 }
341
342 const { affinities } = this._computeAffinity(localProcessExtensions, ExtensionHostKind.LocalProcess, false);
343 for (const extension of localProcessExtensions) {
344 const affinity = affinities.get(extension.identifier) || 0;
345 this._runningLocation.set(extension.identifier, new LocalProcessRunningLocation(affinity));
346 }
347
348 const { affinities: webWorkerExtensionsAffinities } = this._computeAffinity(localWebWorkerExtensions, ExtensionHostKind.LocalWebWorker, false);
349 for (const extension of localWebWorkerExtensions) {
350 const affinity = webWorkerExtensionsAffinities.get(extension.identifier) || 0;
351 this._runningLocation.set(extension.identifier, new LocalWebWorkerRunningLocation(affinity));
352 }
353 }
355 >
356 > export function filterExtensionDescriptions(extensions: readonly IExtensionDescription[], runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, predicate: (extRunningLocation: ExtensionRunningLocation) => boolean): IExtensionDescription[] {
357 return extensions.filter((ext) => {
358 const extRunningLocation = runningLocation.get(ext.identifier);
359 return extRunningLocation && predicate(extRunningLocation);
360 });
361 }
363 > export function filterExtensionIdentifiers(extensions: readonly ExtensionIdentifier[], runningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>, predicate: (extRunningLocation: ExtensionRunningLocation) => boolean): ExtensionIdentifier[] {
364 return extensions.filter((ext) => {
365 const extRunningLocation = runningLocation.get(ext);
366 return extRunningLocation && predicate(extRunningLocation);
367 });
368 }