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