Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensions/common/abstractExtensionService.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 { Barrier } from '../../../../base/common/async.js';
7
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
8
import { Emitter } from '../../../../base/common/event.js';
9
import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
10
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
11
import { Schemas } from '../../../../base/common/network.js';
12
import * as perf from '../../../../base/common/performance.js';
13
import { isCI } from '../../../../base/common/platform.js';
14
import { isEqualOrParent } from '../../../../base/common/resources.js';
15
import { StopWatch } from '../../../../base/common/stopwatch.js';
16
import { isDefined } from '../../../../base/common/types.js';
17
import { URI } from '../../../../base/common/uri.js';
18
import * as nls from '../../../../nls.js';
19
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
20
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
21
import { InstallOperation } from '../../../../platform/extensionManagement/common/extensionManagement.js';
22
import { ImplicitActivationEvents } from '../../../../platform/extensionManagement/common/implicitActivationEvents.js';
23
import { ExtensionIdentifier, ExtensionIdentifierMap, IExtension, IExtensionContributions, IExtensionDescription, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';
24
import { IFileService } from '../../../../platform/files/common/files.js';
25
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
26
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
27
import { handleVetos } from '../../../../platform/lifecycle/common/lifecycle.js';
28
import { ILogService } from '../../../../platform/log/common/log.js';
29
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
30
import { IProductService } from '../../../../platform/product/common/productService.js';
31
import { Registry } from '../../../../platform/registry/common/platform.js';
32
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
33
import { IRemoteExtensionsScannerService } from '../../../../platform/remote/common/remoteExtensionsScanner.js';
34
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
35
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
36
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
37
import { IExtensionFeaturesRegistry, Extensions as ExtensionFeaturesExtensions, IExtensionFeatureMarkdownRenderer, IRenderedData, } from '../../extensionManagement/common/extensionFeatures.js';
38
import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../extensionManagement/common/extensionManagement.js';
39
import { ExtensionDescriptionRegistryLock, ExtensionDescriptionRegistrySnapshot, IActivationEventsReader, LockableExtensionDescriptionRegistry } from './extensionDescriptionRegistry.js';
40
import { parseExtensionDevOptions } from './extensionDevOptions.js';
41
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker } from './extensionHostKind.js';
42
import { ExtensionHostManager } from './extensionHostManager.js';
43
import { IExtensionHostManager } from './extensionHostManagers.js';
44
import { IResolveAuthorityErrorResult } from './extensionHostProxy.js';
45
import { IExtensionManifestPropertiesService } from './extensionManifestPropertiesService.js';
46
import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, RemoteRunningLocation } from './extensionRunningLocation.js';
47
import { ExtensionRunningLocationTracker, filterExtensionIdentifiers } from './extensionRunningLocationTracker.js';
48
import { ActivationKind, ActivationTimes, ExtensionActivationReason, ExtensionHostStartup, ExtensionPointContribution, IExtensionHost, IExtensionInspectInfo, IExtensionService, IExtensionsStatus, IInternalExtensionService, IMessage, IResponsiveStateChangeEvent, IWillActivateEvent, WillStopExtensionHostsEvent, toExtension, toExtensionDescription } from './extensions.js';
49
import { ExtensionsProposedApi } from './extensionsProposedApi.js';
50
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from './extensionsRegistry.js';
51
import { LazyCreateExtensionHostManager } from './lazyCreateExtensionHostManager.js';
52
import { ResponsiveState } from './rpcProtocol.js';
53
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkActivateWorkspaceContainsExtension, checkGlobFileExists } from './workspaceContains.js';
54
import { ILifecycleService, WillShutdownJoinerOrder } from '../../lifecycle/common/lifecycle.js';
55
import { IExtensionHostExitInfo, IRemoteAgentService } from '../../remote/common/remoteAgentService.js';
56
57
const hasOwnProperty = Object.hasOwnProperty;
58
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
59
60
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
61
62
public _serviceBrand: undefined;
63
64
private readonly _hasLocalProcess: boolean;
65
private readonly _allowRemoteExtensionsInLocalWebWorker: boolean;
66
67
private readonly _onDidRegisterExtensions = this._register(new Emitter<void>());
68
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
69
70
private readonly _onDidChangeExtensionsStatus = this._register(new Emitter<ExtensionIdentifier[]>());
71
public readonly onDidChangeExtensionsStatus = this._onDidChangeExtensionsStatus.event;
72
73
private readonly _onDidChangeExtensions = this._register(new Emitter<{ readonly added: ReadonlyArray<IExtensionDescription>; readonly removed: ReadonlyArray<IExtensionDescription> }>({ leakWarningThreshold: 400 }));
74
public readonly onDidChangeExtensions = this._onDidChangeExtensions.event;
75
76
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
77
public readonly onWillActivateByEvent = this._onWillActivateByEvent.event;
78
79
private readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
80
public readonly onDidChangeResponsiveChange = this._onDidChangeResponsiveChange.event;
81
82
private readonly _onWillStop = this._register(new Emitter<WillStopExtensionHostsEvent>());
83
public readonly onWillStop = this._onWillStop.event;
84
85
private readonly _activationEventReader = new ImplicitActivationAwareReader();
86
private readonly _registry = new LockableExtensionDescriptionRegistry(this._activationEventReader);
87
private readonly _installedExtensionsReady = new Barrier();
88
private readonly _extensionStatus = new ExtensionIdentifierMap<ExtensionStatus>();
89
private readonly _allRequestedActivateEvents = new Set<string>();
90
private readonly _runningLocations: ExtensionRunningLocationTracker;
91
private readonly _remoteCrashTracker = new ExtensionHostCrashTracker();
92
93
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[] = [];
94
private _inHandleDeltaExtensions = false;
95
96
private readonly _extensionHostManagers = this._register(new ExtensionHostCollection());
97
98
private _resolveAuthorityAttempt: number = 0;
99
100
constructor(
101
options: { hasLocalProcess: boolean; allowRemoteExtensionsInLocalWebWorker: boolean },
102
private readonly _extensionsProposedApi: ExtensionsProposedApi,
103
private readonly _extensionHostFactory: IExtensionHostFactory,
104
private readonly _extensionHostKindPicker: IExtensionHostKindPicker,
105
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
106
@INotificationService protected readonly _notificationService: INotificationService,
107
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
108
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
109
@IWorkbenchExtensionEnablementService protected readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
110
@IFileService protected readonly _fileService: IFileService,
111
@IProductService protected readonly _productService: IProductService,
112
@IWorkbenchExtensionManagementService protected readonly _extensionManagementService: IWorkbenchExtensionManagementService,
113
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
114
@IConfigurationService protected readonly _configurationService: IConfigurationService,
115
@IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
116
@ILogService protected readonly _logService: ILogService,
117
@IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService,
118
@IRemoteExtensionsScannerService protected readonly _remoteExtensionsScannerService: IRemoteExtensionsScannerService,
119
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
120
@IRemoteAuthorityResolverService protected readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
121
@IDialogService private readonly _dialogService: IDialogService,
122
) {
123
super();
124
125
this._hasLocalProcess = options.hasLocalProcess;
126
this._allowRemoteExtensionsInLocalWebWorker = options.allowRemoteExtensionsInLocalWebWorker;
127
128
// help the file service to activate providers by activating extensions by file system event
129
this._register(this._fileService.onWillActivateFileSystemProvider(e => {
130
if (e.scheme !== Schemas.vscodeRemote) {
131
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
132
}
133
}));
134
135
this._runningLocations = new ExtensionRunningLocationTracker(
136
this._registry,
137
this._extensionHostKindPicker,
138
this._environmentService,
139
this._configurationService,
140
this._logService,
141
this._extensionManifestPropertiesService
142
);
143
144
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
145
const toAdd: IExtension[] = [];
146
const toRemove: IExtension[] = [];
147
for (const extension of extensions) {
148
if (this._safeInvokeIsEnabled(extension)) {
149
// an extension has been enabled
150
toAdd.push(extension);
151
} else {
152
// an extension has been disabled
153
toRemove.push(extension);
154
}
155
}
156
if (isCI) {
157
this._logService.info(`AbstractExtensionService.onEnablementChanged fired for ${extensions.map(e => e.identifier.id).join(', ')}`);
158
}
159
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
160
}));
161
162
this._register(this._extensionManagementService.onDidChangeProfile(({ added, removed }) => {
163
if (added.length || removed.length) {
164
if (isCI) {
165
this._logService.info(`AbstractExtensionService.onDidChangeProfile fired`);
166
}
167
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(added, removed));
168
}
169
}));
170
171
this._register(this._extensionManagementService.onDidEnableExtensions(extensions => {
172
if (extensions.length) {
173
if (isCI) {
174
this._logService.info(`AbstractExtensionService.onDidEnableExtensions fired`);
175
}
176
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(extensions, []));
177
}
178
}));
179
180
this._register(this._extensionManagementService.onDidInstallExtensions((result) => {
181
const extensions: IExtension[] = [];
182
for (const { local, operation } of result) {
183
if (local && local.isValid && operation !== InstallOperation.Migrate && this._safeInvokeIsEnabled(local)) {
184
extensions.push(local);
185
}
186
}
187
if (extensions.length) {
188
if (isCI) {
189
this._logService.info(`AbstractExtensionService.onDidInstallExtensions fired for ${extensions.map(e => e.identifier.id).join(', ')}`);
190
}
191
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(extensions, []));
192
}
193
}));
194
195
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
196
if (!event.error) {
197
// an extension has been uninstalled
198
if (isCI) {
199
this._logService.info(`AbstractExtensionService.onDidUninstallExtension fired for ${event.identifier.id}`);
200
}
201
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
202
}
203
}));
204
205
this._register(this._lifecycleService.onWillShutdown(event => {
206
if (this._remoteAgentService.getConnection()) {
207
event.join(async () => {
208
// We need to disconnect the management connection before killing the local extension host.
209
// Otherwise, the local extension host might terminate the underlying tunnel before the
210
// management connection has a chance to send its disconnection message.
211
try {
212
await this._remoteAgentService.endConnection();
213
await this._doStopExtensionHosts();
214
this._remoteAgentService.getConnection()?.dispose();
215
} catch {
216
this._logService.warn('Error while disconnecting remote agent');
217
}
218
}, {
219
id: 'join.disconnectRemote',
220
label: nls.localize('disconnectRemote', "Disconnect Remote Agent"),
221
order: WillShutdownJoinerOrder.Last // after others have joined that might depend on a remote connection
222
});
223
} else {
224
event.join(this._doStopExtensionHosts(), {
225
id: 'join.stopExtensionHosts',
226
label: nls.localize('stopExtensionHosts', "Stopping Extension Hosts"),
227
});
228
}
229
}));
230
}
231
232
protected _getExtensionHostManagers(kind: ExtensionHostKind): IExtensionHostManager[] {
233
return this._extensionHostManagers.getByKind(kind);
234
}
235
236
//#region deltaExtensions
237
238
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
239
this._deltaExtensionsQueue.push(item);
240
if (this._inHandleDeltaExtensions) {
241
// Let the current item finish, the new one will be picked up
242
return;
243
}
244
245
let lock: ExtensionDescriptionRegistryLock | null = null;
246
try {
247
this._inHandleDeltaExtensions = true;
248
249
// wait for _initialize to finish before hanlding any delta extension events
250
await this._installedExtensionsReady.wait();
251
252
lock = await this._registry.acquireLock('handleDeltaExtensions');
253
while (this._deltaExtensionsQueue.length > 0) {
254
const item = this._deltaExtensionsQueue.shift()!;
255
await this._deltaExtensions(lock, item.toAdd, item.toRemove);
256
}
257
} finally {
258
this._inHandleDeltaExtensions = false;
259
lock?.dispose();
260
}
261
}
262
263
private async _deltaExtensions(lock: ExtensionDescriptionRegistryLock, _toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise<void> {
264
if (isCI) {
265
this._logService.info(`AbstractExtensionService._deltaExtensions: toAdd: [${_toAdd.map(e => e.identifier.id).join(',')}] toRemove: [${_toRemove.map(e => typeof e === 'string' ? e : e.identifier.id).join(',')}]`);
266
}
267
let toRemove: IExtensionDescription[] = [];
268
for (let i = 0, len = _toRemove.length; i < len; i++) {
269
const extensionOrId = _toRemove[i];
270
const extensionId = (typeof extensionOrId === 'string' ? extensionOrId : extensionOrId.identifier.id);
271
const extension = (typeof extensionOrId === 'string' ? null : extensionOrId);
272
const extensionDescription = this._registry.getExtensionDescription(extensionId);
273
if (!extensionDescription) {
274
// ignore disabling/uninstalling an extension which is not running
275
continue;
276
}
277
278
if (extension && extensionDescription.extensionLocation.scheme !== extension.location.scheme) {
279
// this event is for a different extension than mine (maybe for the local extension, while I have the remote extension)
280
continue;
281
}
282
283
if (!this.canRemoveExtension(extensionDescription)) {
284
// uses non-dynamic extension point or is activated
285
continue;
286
}
287
288
toRemove.push(extensionDescription);
289
}
290
291
const toAdd: IExtensionDescription[] = [];
292
for (let i = 0, len = _toAdd.length; i < len; i++) {
293
const extension = _toAdd[i];
294
295
const extensionDescription = toExtensionDescription(extension, false);
296
if (!extensionDescription) {
297
// could not scan extension...
298
continue;
299
}
300
301
if (!this._canAddExtension(extensionDescription, toRemove)) {
302
continue;
303
}
304
305
toAdd.push(extensionDescription);
306
}
307
308
if (toAdd.length === 0 && toRemove.length === 0) {
309
return;
310
}
311
312
// Update the local registry
313
const result = this._registry.deltaExtensions(lock, toAdd, toRemove.map(e => e.identifier));
314
this._onDidChangeExtensions.fire({ added: toAdd, removed: toRemove });
315
316
toRemove = toRemove.concat(result.removedDueToLooping);
317
if (result.removedDueToLooping.length > 0) {
318
this._notificationService.notify({
319
severity: Severity.Error,
320
message: nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))
321
});
322
}
323
324
// enable or disable proposed API per extension
325
this._extensionsProposedApi.updateEnabledApiProposals(toAdd);
326
327
// Update extension points
328
this._doHandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove), false);
329
330
// Update the extension host
331
await this._updateExtensionsOnExtHosts(result.versionId, toAdd, toRemove.map(e => e.identifier));
332
333
for (let i = 0; i < toAdd.length; i++) {
334
this._activateAddedExtensionIfNeeded(toAdd[i]);
335
}
336
}
337
338
private async _updateExtensionsOnExtHosts(versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
339
const removedRunningLocation = this._runningLocations.deltaExtensions(toAdd, toRemove);
340
const promises = this._extensionHostManagers.map(
341
extHostManager => this._updateExtensionsOnExtHost(extHostManager, versionId, toAdd, toRemove, removedRunningLocation)
342
);
343
await Promise.all(promises);
344
}
345
346
private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[], removedRunningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>): Promise<void> {
347
const myToAdd = this._runningLocations.filterByExtensionHostManager(toAdd, extensionHostManager);
348
const myToRemove = filterExtensionIdentifiers(toRemove, removedRunningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation));
349
const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd);
350
if (isCI) {
351
const printExtIds = (extensions: IExtensionDescription[]) => extensions.map(e => e.identifier.value).join(',');
352
const printIds = (extensions: ExtensionIdentifier[]) => extensions.map(e => e.value).join(',');
353
this._logService.info(`AbstractExtensionService: Calling deltaExtensions: toRemove: [${printIds(toRemove)}], toAdd: [${printExtIds(toAdd)}], myToRemove: [${printIds(myToRemove)}], myToAdd: [${printExtIds(myToAdd)}],`);
354
}
355
await extensionHostManager.deltaExtensions({ versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd: myToAdd.map(extension => extension.identifier) });
356
}
357
358
public canAddExtension(extension: IExtensionDescription): boolean {
359
return this._canAddExtension(extension, []);
360
}
361
362
private _canAddExtension(extension: IExtensionDescription, extensionsBeingRemoved: IExtensionDescription[]): boolean {
363
// (Also check for renamed extensions)
364
const existing = this._registry.getExtensionDescriptionByIdOrUUID(extension.identifier, extension.id);
365
if (existing) {
366
// This extension is already known (most likely at a different version)
367
// so it cannot be added again unless it is removed first
368
const isBeingRemoved = extensionsBeingRemoved.some((extensionDescription) => ExtensionIdentifier.equals(extension.identifier, extensionDescription.identifier));
369
if (!isBeingRemoved) {
370
return false;
371
}
372
}
373
374
const extensionKinds = this._runningLocations.readExtensionKinds(extension);
375
const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;
376
const extensionHostKind = this._extensionHostKindPicker.pickExtensionHostKind(extension.identifier, extensionKinds, !isRemote, isRemote, ExtensionRunningPreference.None);
377
if (extensionHostKind === null) {
378
return false;
379
}
380
381
return true;
382
}
383
384
public canRemoveExtension(extension: IExtensionDescription): boolean {
385
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
386
if (!extensionDescription) {
387
// Can't remove an extension that is unknown!
388
return false;
389
}
390
391
if (this._extensionStatus.get(extensionDescription.identifier)?.activationStarted) {
392
// Extension is running, cannot remove it safely
393
return false;
394
}
395
396
return true;
397
}
398
399
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
400
let shouldActivate = false;
401
let shouldActivateReason: string | null = null;
402
let hasWorkspaceContains = false;
403
const activationEvents = this._activationEventReader.readActivationEvents(extensionDescription);
404
for (const activationEvent of activationEvents) {
405
if (this._allRequestedActivateEvents.has(activationEvent)) {
406
// This activation event was fired before the extension was added
407
shouldActivate = true;
408
shouldActivateReason = activationEvent;
409
break;
410
}
411
412
if (activationEvent === '*') {
413
shouldActivate = true;
414
shouldActivateReason = activationEvent;
415
break;
416
}
417
418
if (/^workspaceContains/.test(activationEvent)) {
419
hasWorkspaceContains = true;
420
}
421
422
if (activationEvent === 'onStartupFinished') {
423
shouldActivate = true;
424
shouldActivateReason = activationEvent;
425
break;
426
}
427
}
428
429
if (shouldActivate) {
430
await Promise.all(
431
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
432
).then(() => { });
433
} else if (hasWorkspaceContains) {
434
const workspace = await this._contextService.getCompleteWorkspace();
435
const forceUsingSearch = !!this._environmentService.remoteAuthority;
436
const host: IWorkspaceContainsActivationHost = {
437
logService: this._logService,
438
folders: workspace.folders.map(folder => folder.uri),
439
forceUsingSearch: forceUsingSearch,
440
exists: (uri) => this._fileService.exists(uri),
441
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
442
};
443
444
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
445
if (!result) {
446
return;
447
}
448
449
await Promise.all(
450
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
451
).then(() => { });
452
}
453
}
454
455
//#endregion
456
457
protected async _initialize(): Promise<void> {
458
perf.mark('code/willLoadExtensions');
459
this._startExtensionHostsIfNecessary(true, []);
460
461
const lock = await this._registry.acquireLock('_initialize');
462
try {
463
await this._resolveAndProcessExtensions(lock);
464
// Start extension hosts which are not automatically started
465
this._startOnDemandExtensionHosts();
466
} finally {
467
lock.dispose();
468
}
469
470
this._releaseBarrier();
471
perf.mark('code/didLoadExtensions');
472
await this._handleExtensionTests();
473
}
474
475
private async _resolveAndProcessExtensions(lock: ExtensionDescriptionRegistryLock,): Promise<void> {
476
let resolverExtensions: IExtensionDescription[] = [];
477
let localExtensions: IExtensionDescription[] = [];
478
let remoteExtensions: IExtensionDescription[] = [];
479
480
for await (const extensions of this._resolveExtensions()) {
481
if (extensions instanceof ResolverExtensions) {
482
resolverExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, extensions.extensions, false);
483
this._registry.deltaExtensions(lock, resolverExtensions, []);
484
this._doHandleExtensionPoints(resolverExtensions, true);
485
}
486
if (extensions instanceof LocalExtensions) {
487
localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, extensions.extensions, false);
488
}
489
if (extensions instanceof RemoteExtensions) {
490
remoteExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, extensions.extensions, false);
491
}
492
}
493
494
// `initializeRunningLocation` will look at the complete picture (e.g. an extension installed on both sides),
495
// takes care of duplicates and picks a running location for each extension
496
this._runningLocations.initializeRunningLocation(localExtensions, remoteExtensions);
497
498
this._startExtensionHostsIfNecessary(true, []);
499
500
// Some remote extensions could run locally in the web worker, so store them
501
const remoteExtensionsThatNeedToRunLocally = (this._allowRemoteExtensionsInLocalWebWorker ? this._runningLocations.filterByExtensionHostKind(remoteExtensions, ExtensionHostKind.LocalWebWorker) : []);
502
const localProcessExtensions = (this._hasLocalProcess ? this._runningLocations.filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalProcess) : []);
503
const localWebWorkerExtensions = this._runningLocations.filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalWebWorker);
504
remoteExtensions = this._runningLocations.filterByExtensionHostKind(remoteExtensions, ExtensionHostKind.Remote);
505
506
// Add locally the remote extensions that need to run locally in the web worker
507
for (const ext of remoteExtensionsThatNeedToRunLocally) {
508
if (!includes(localWebWorkerExtensions, ext.identifier)) {
509
localWebWorkerExtensions.push(ext);
510
}
511
}
512
513
const allExtensions = remoteExtensions.concat(localProcessExtensions).concat(localWebWorkerExtensions);
514
let toAdd = allExtensions;
515
516
if (resolverExtensions.length) {
517
// Add extensions that are not registered as resolvers but are in the final resolved set
518
toAdd = allExtensions.filter(extension => !resolverExtensions.some(e => ExtensionIdentifier.equals(e.identifier, extension.identifier) && e.extensionLocation.toString() === extension.extensionLocation.toString()));
519
// Remove extensions that are registered as resolvers but are not in the final resolved set
520
if (allExtensions.length < toAdd.length + resolverExtensions.length) {
521
const toRemove = resolverExtensions.filter(registered => !allExtensions.some(e => ExtensionIdentifier.equals(e.identifier, registered.identifier) && e.extensionLocation.toString() === registered.extensionLocation.toString()));
522
if (toRemove.length) {
523
this._registry.deltaExtensions(lock, [], toRemove.map(e => e.identifier));
524
this._doHandleExtensionPoints(toRemove, true);
525
}
526
}
527
}
528
529
const result = this._registry.deltaExtensions(lock, toAdd, []);
530
if (result.removedDueToLooping.length > 0) {
531
this._notificationService.notify({
532
severity: Severity.Error,
533
message: nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))
534
});
535
}
536
537
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions(), false);
538
}
539
540
private async _handleExtensionTests(): Promise<void> {
541
if (!this._environmentService.isExtensionDevelopment || !this._environmentService.extensionTestsLocationURI) {
542
return;
543
}
544
545
const extensionHostManager = this.findTestExtensionHost(this._environmentService.extensionTestsLocationURI);
546
if (!extensionHostManager) {
547
const msg = nls.localize('extensionTestError', "No extension host found that can launch the test runner at {0}.", this._environmentService.extensionTestsLocationURI.toString());
548
console.error(msg);
549
this._notificationService.error(msg);
550
return;
551
}
552
553
554
let exitCode: number;
555
try {
556
exitCode = await extensionHostManager.extensionTestsExecute();
557
if (isCI) {
558
this._logService.info(`Extension host test runner exit code: ${exitCode}`);
559
}
560
} catch (err) {
561
if (isCI) {
562
this._logService.error(`Extension host test runner error`, err);
563
}
564
console.error(err);
565
exitCode = 1 /* ERROR */;
566
}
567
568
this._onExtensionHostExit(exitCode);
569
}
570
571
private findTestExtensionHost(testLocation: URI): IExtensionHostManager | null {
572
let runningLocation: ExtensionRunningLocation | null = null;
573
574
for (const extension of this._registry.getAllExtensionDescriptions()) {
575
if (isEqualOrParent(testLocation, extension.extensionLocation)) {
576
runningLocation = this._runningLocations.getRunningLocation(extension.identifier);
577
break;
578
}
579
}
580
if (runningLocation === null) {
581
// not sure if we should support that, but it was possible to have an test outside an extension
582
583
if (testLocation.scheme === Schemas.vscodeRemote) {
584
runningLocation = new RemoteRunningLocation();
585
} else {
586
// When a debugger attaches to the extension host, it will surface all console.log messages from the extension host,
587
// but not necessarily from the window. So it would be best if any errors get printed to the console of the extension host.
588
// That is why here we use the local process extension host even for non-file URIs
589
runningLocation = new LocalProcessRunningLocation(0);
590
}
591
}
592
if (runningLocation !== null) {
593
return this._extensionHostManagers.getByRunningLocation(runningLocation);
594
}
595
return null;
596
}
597
598
private _releaseBarrier(): void {
599
this._installedExtensionsReady.open();
600
this._onDidRegisterExtensions.fire(undefined);
601
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
602
}
603
604
//#region remote authority resolving
605
606
protected async _resolveAuthorityInitial(remoteAuthority: string): Promise<ResolverResult> {
607
const MAX_ATTEMPTS = 5;
608
609
for (let attempt = 1; ; attempt++) {
610
try {
611
return this._resolveAuthorityWithLogging(remoteAuthority);
612
} catch (err) {
613
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
614
// There is no point in retrying if there is no resolver found
615
throw err;
616
}
617
618
if (RemoteAuthorityResolverError.isNotAvailable(err)) {
619
// The resolver is not available and asked us to not retry
620
throw err;
621
}
622
623
if (attempt >= MAX_ATTEMPTS) {
624
// Too many failed attempts, give up
625
throw err;
626
}
627
}
628
}
629
}
630
631
protected async _resolveAuthorityAgain(): Promise<void> {
632
const remoteAuthority = this._environmentService.remoteAuthority;
633
if (!remoteAuthority) {
634
return;
635
}
636
637
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
638
try {
639
const result = await this._resolveAuthorityWithLogging(remoteAuthority);
640
this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options);
641
} catch (err) {
642
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
643
}
644
}
645
646
private async _resolveAuthorityWithLogging(remoteAuthority: string): Promise<ResolverResult> {
647
const authorityPrefix = getRemoteAuthorityPrefix(remoteAuthority);
648
const sw = StopWatch.create(false);
649
this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`);
650
try {
651
perf.mark(`code/willResolveAuthority/${authorityPrefix}`);
652
const result = await this._resolveAuthority(remoteAuthority);
653
perf.mark(`code/didResolveAuthorityOK/${authorityPrefix}`);
654
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.connectTo}' after ${sw.elapsed()} ms`);
655
return result;
656
} catch (err) {
657
perf.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
658
this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err);
659
throw err;
660
}
661
}
662
663
protected async _resolveAuthorityOnExtensionHosts(kind: ExtensionHostKind, remoteAuthority: string): Promise<ResolverResult> {
664
665
const extensionHosts = this._getExtensionHostManagers(kind);
666
if (extensionHosts.length === 0) {
667
// no local process extension hosts
668
throw new Error(`Cannot resolve authority`);
669
}
670
671
this._resolveAuthorityAttempt++;
672
const results = await Promise.all(extensionHosts.map(extHost => extHost.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt)));
673
674
let bestErrorResult: IResolveAuthorityErrorResult | null = null;
675
for (const result of results) {
676
if (result.type === 'ok') {
677
return result.value;
678
}
679
if (!bestErrorResult) {
680
bestErrorResult = result;
681
continue;
682
}
683
const bestErrorIsUnknown = (bestErrorResult.error.code === RemoteAuthorityResolverErrorCode.Unknown);
684
const errorIsUnknown = (result.error.code === RemoteAuthorityResolverErrorCode.Unknown);
685
if (bestErrorIsUnknown && !errorIsUnknown) {
686
bestErrorResult = result;
687
}
688
}
689
690
// we can only reach this if there is an error
691
throw new RemoteAuthorityResolverError(bestErrorResult!.error.message, bestErrorResult!.error.code, bestErrorResult!.error.detail);
692
}
693
694
//#endregion
695
696
//#region Stopping / Starting / Restarting
697
698
public stopExtensionHosts(reason: string, auto?: boolean): Promise<boolean> {
699
return this._doStopExtensionHostsWithVeto(reason, auto);
700
}
701
702
protected async _doStopExtensionHosts(): Promise<void> {
703
const previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
704
for (const extensionStatus of this._extensionStatus.values()) {
705
if (extensionStatus.activationStarted) {
706
previouslyActivatedExtensionIds.push(extensionStatus.id);
707
}
708
}
709
710
await this._extensionHostManagers.stopAllInReverse();
711
for (const extensionStatus of this._extensionStatus.values()) {
712
extensionStatus.clearRuntimeStatus();
713
}
714
715
if (previouslyActivatedExtensionIds.length > 0) {
716
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
717
}
718
}
719
720
private async _doStopExtensionHostsWithVeto(reason: string, auto: boolean = false): Promise<boolean> {
721
if (auto && this._environmentService.isExtensionDevelopment) {
722
return false;
723
}
724
725
const vetos: (boolean | Promise<boolean>)[] = [];
726
const vetoReasons = new Set<string>();
727
728
this._onWillStop.fire({
729
reason,
730
auto,
731
veto(value, reason) {
732
vetos.push(value);
733
734
if (typeof value === 'boolean') {
735
if (value === true) {
736
vetoReasons.add(reason);
737
}
738
} else {
739
value.then(value => {
740
if (value) {
741
vetoReasons.add(reason);
742
}
743
}).catch(error => {
744
vetoReasons.add(nls.localize('extensionStopVetoError', "{0} (Error: {1})", reason, toErrorMessage(error)));
745
});
746
}
747
}
748
});
749
750
const veto = await handleVetos(vetos, error => this._logService.error(error));
751
if (!veto) {
752
await this._doStopExtensionHosts();
753
} else {
754
if (!auto) {
755
const vetoReasonsArray = Array.from(vetoReasons);
756
757
this._logService.warn(`Extension host was not stopped because of veto (stop reason: ${reason}, veto reason: ${vetoReasonsArray.join(', ')})`);
758
759
const { confirmed } = await this._dialogService.confirm({
760
type: Severity.Warning,
761
message: nls.localize('extensionStopVetoMessage', "Please confirm restart of extensions."),
762
detail: vetoReasonsArray.length === 1 ?
763
vetoReasonsArray[0] :
764
vetoReasonsArray.join('\n -'),
765
primaryButton: nls.localize('proceedAnyways', "Restart Anyway")
766
});
767
768
if (confirmed) {
769
return true;
770
}
771
}
772
773
}
774
775
return !veto;
776
}
777
778
private _startExtensionHostsIfNecessary(isInitialStart: boolean, initialActivationEvents: string[]): void {
779
const locations: ExtensionRunningLocation[] = [];
780
for (let affinity = 0; affinity <= this._runningLocations.maxLocalProcessAffinity; affinity++) {
781
locations.push(new LocalProcessRunningLocation(affinity));
782
}
783
for (let affinity = 0; affinity <= this._runningLocations.maxLocalWebWorkerAffinity; affinity++) {
784
locations.push(new LocalWebWorkerRunningLocation(affinity));
785
}
786
locations.push(new RemoteRunningLocation());
787
for (const location of locations) {
788
if (this._extensionHostManagers.getByRunningLocation(location)) {
789
// already running
790
continue;
791
}
792
const res = this._createExtensionHostManager(location, isInitialStart, initialActivationEvents);
793
if (res) {
794
const [extHostManager, disposableStore] = res;
795
this._extensionHostManagers.add(extHostManager, disposableStore);
796
}
797
}
798
}
799
800
private _createExtensionHostManager(runningLocation: ExtensionRunningLocation, isInitialStart: boolean, initialActivationEvents: string[]): null | [IExtensionHostManager, DisposableStore] {
801
const extensionHost = this._extensionHostFactory.createExtensionHost(this._runningLocations, runningLocation, isInitialStart);
802
if (!extensionHost) {
803
return null;
804
}
805
806
const processManager: IExtensionHostManager = this._doCreateExtensionHostManager(extensionHost, initialActivationEvents);
807
const disposableStore = new DisposableStore();
808
disposableStore.add(processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal)));
809
disposableStore.add(processManager.onDidChangeResponsiveState((responsiveState) => {
810
this._logService.info(`Extension host (${processManager.friendyName}) is ${responsiveState === ResponsiveState.Responsive ? 'responsive' : 'unresponsive'}.`);
811
this._onDidChangeResponsiveChange.fire({
812
extensionHostKind: processManager.kind,
813
isResponsive: responsiveState === ResponsiveState.Responsive,
814
getInspectListener: (tryEnableInspector: boolean) => {
815
return processManager.getInspectPort(tryEnableInspector);
816
}
817
});
818
}));
819
return [processManager, disposableStore];
820
}
821
822
protected _doCreateExtensionHostManager(extensionHost: IExtensionHost, initialActivationEvents: string[]): IExtensionHostManager {
823
const internalExtensionService = this._acquireInternalAPI(extensionHost);
824
if (extensionHost.startup === ExtensionHostStartup.LazyAutoStart) {
825
return this._instantiationService.createInstance(LazyCreateExtensionHostManager, extensionHost, initialActivationEvents, internalExtensionService);
826
}
827
return this._instantiationService.createInstance(ExtensionHostManager, extensionHost, initialActivationEvents, internalExtensionService);
828
}
829
830
private _onExtensionHostCrashOrExit(extensionHost: IExtensionHostManager, code: number, signal: string | null): void {
831
832
// Unexpected termination
833
const isExtensionDevHost = parseExtensionDevOptions(this._environmentService).isExtensionDevHost;
834
if (!isExtensionDevHost) {
835
this._onExtensionHostCrashed(extensionHost, code, signal);
836
return;
837
}
838
839
this._onExtensionHostExit(code);
840
}
841
842
protected _onExtensionHostCrashed(extensionHost: IExtensionHostManager, code: number, signal: string | null): void {
843
console.error(`Extension host (${extensionHost.friendyName}) terminated unexpectedly. Code: ${code}, Signal: ${signal}`);
844
if (extensionHost.kind === ExtensionHostKind.LocalProcess) {
845
this._doStopExtensionHosts();
846
} else if (extensionHost.kind === ExtensionHostKind.Remote) {
847
if (signal) {
848
this._onRemoteExtensionHostCrashed(extensionHost, signal);
849
}
850
this._extensionHostManagers.stopOne(extensionHost);
851
}
852
}
853
854
private _getExtensionHostExitInfoWithTimeout(reconnectionToken: string): Promise<IExtensionHostExitInfo | null> {
855
return new Promise((resolve, reject) => {
856
const timeoutHandle = setTimeout(() => {
857
reject(new Error('getExtensionHostExitInfo timed out'));
858
}, 2000);
859
this._remoteAgentService.getExtensionHostExitInfo(reconnectionToken).then(
860
(r) => {
861
clearTimeout(timeoutHandle);
862
resolve(r);
863
},
864
reject
865
);
866
});
867
}
868
869
private async _onRemoteExtensionHostCrashed(extensionHost: IExtensionHostManager, reconnectionToken: string): Promise<void> {
870
try {
871
const info = await this._getExtensionHostExitInfoWithTimeout(reconnectionToken);
872
if (info) {
873
this._logService.error(`Extension host (${extensionHost.friendyName}) terminated unexpectedly with code ${info.code}.`);
874
}
875
876
this._logExtensionHostCrash(extensionHost);
877
this._remoteCrashTracker.registerCrash();
878
879
if (this._remoteCrashTracker.shouldAutomaticallyRestart()) {
880
this._logService.info(`Automatically restarting the remote extension host.`);
881
this._notificationService.status(nls.localize('extensionService.autoRestart', "The remote extension host terminated unexpectedly. Restarting..."), { hideAfter: 5000 });
882
this._startExtensionHostsIfNecessary(false, Array.from(this._allRequestedActivateEvents.keys()));
883
} else {
884
this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Remote Extension host terminated unexpectedly 3 times within the last 5 minutes."),
885
[{
886
label: nls.localize('restart', "Restart Remote Extension Host"),
887
run: () => {
888
this._startExtensionHostsIfNecessary(false, Array.from(this._allRequestedActivateEvents.keys()));
889
}
890
}]
891
);
892
}
893
} catch (err) {
894
// maybe this wasn't an extension host crash and it was a permanent disconnection
895
}
896
}
897
898
protected _logExtensionHostCrash(extensionHost: IExtensionHostManager): void {
899
900
const activatedExtensions: ExtensionIdentifier[] = [];
901
for (const extensionStatus of this._extensionStatus.values()) {
902
if (extensionStatus.activationStarted && extensionHost.containsExtension(extensionStatus.id)) {
903
activatedExtensions.push(extensionStatus.id);
904
}
905
}
906
907
if (activatedExtensions.length > 0) {
908
this._logService.error(`Extension host (${extensionHost.friendyName}) terminated unexpectedly. The following extensions were running: ${activatedExtensions.map(id => id.value).join(', ')}`);
909
} else {
910
this._logService.error(`Extension host (${extensionHost.friendyName}) terminated unexpectedly. No extensions were activated.`);
911
}
912
}
913
914
public async startExtensionHosts(updates?: { toAdd: IExtension[]; toRemove: string[] }): Promise<void> {
915
await this._doStopExtensionHosts();
916
917
if (updates) {
918
await this._handleDeltaExtensions(new DeltaExtensionsQueueItem(updates.toAdd, updates.toRemove));
919
}
920
921
const lock = await this._registry.acquireLock('startExtensionHosts');
922
try {
923
this._startExtensionHostsIfNecessary(false, Array.from(this._allRequestedActivateEvents.keys()));
924
this._startOnDemandExtensionHosts();
925
926
const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess);
927
await Promise.all(localProcessExtensionHosts.map(extHost => extHost.ready()));
928
} finally {
929
lock.dispose();
930
}
931
}
932
933
private _startOnDemandExtensionHosts(): void {
934
const snapshot = this._registry.getSnapshot();
935
for (const extHostManager of this._extensionHostManagers) {
936
if (extHostManager.startup !== ExtensionHostStartup.EagerAutoStart) {
937
const extensions = this._runningLocations.filterByExtensionHostManager(snapshot.extensions, extHostManager);
938
extHostManager.start(snapshot.versionId, snapshot.extensions, extensions.map(extension => extension.identifier));
939
}
940
}
941
}
942
943
//#endregion
944
945
//#region IExtensionService
946
947
public activateByEvent(activationEvent: string, activationKind: ActivationKind = ActivationKind.Normal): Promise<void> {
948
if (this._installedExtensionsReady.isOpen()) {
949
// Extensions have been scanned and interpreted
950
951
// Record the fact that this activationEvent was requested (in case of a restart)
952
this._allRequestedActivateEvents.add(activationEvent);
953
954
if (!this._registry.containsActivationEvent(activationEvent)) {
955
// There is no extension that is interested in this activation event
956
return NO_OP_VOID_PROMISE;
957
}
958
959
return this._activateByEvent(activationEvent, activationKind);
960
} else {
961
// Extensions have not been scanned yet.
962
963
// Record the fact that this activationEvent was requested (in case of a restart)
964
this._allRequestedActivateEvents.add(activationEvent);
965
966
if (activationKind === ActivationKind.Immediate) {
967
// Do not wait for the normal start-up of the extension host(s)
968
return this._activateByEvent(activationEvent, activationKind);
969
}
970
971
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent, activationKind));
972
}
973
}
974
975
private _activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
976
const result = Promise.all(
977
this._extensionHostManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent, activationKind))
978
).then(() => { });
979
this._onWillActivateByEvent.fire({
980
event: activationEvent,
981
activation: result
982
});
983
return result;
984
}
985
986
public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
987
return this._activateById(extensionId, reason);
988
}
989
990
public activationEventIsDone(activationEvent: string): boolean {
991
if (!this._installedExtensionsReady.isOpen()) {
992
return false;
993
}
994
if (!this._registry.containsActivationEvent(activationEvent)) {
995
// There is no extension that is interested in this activation event
996
return true;
997
}
998
return this._extensionHostManagers.every(manager => manager.activationEventIsDone(activationEvent));
999
}
1000
1001
public whenInstalledExtensionsRegistered(): Promise<boolean> {
1002
return this._installedExtensionsReady.wait();
1003
}
1004
1005
get extensions(): IExtensionDescription[] {
1006
return this._registry.getAllExtensionDescriptions();
1007
}
1008
1009
protected _getExtensionRegistrySnapshotWhenReady(): Promise<ExtensionDescriptionRegistrySnapshot> {
1010
return this._installedExtensionsReady.wait().then(() => this._registry.getSnapshot());
1011
}
1012
1013
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
1014
return this._installedExtensionsReady.wait().then(() => {
1015
return this._registry.getExtensionDescription(id);
1016
});
1017
}
1018
1019
public readExtensionPointContributions<T extends IExtensionContributions[keyof IExtensionContributions]>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
1020
return this._installedExtensionsReady.wait().then(() => {
1021
const availableExtensions = this._registry.getAllExtensionDescriptions();
1022
1023
const result: ExtensionPointContribution<T>[] = [];
1024
for (const desc of availableExtensions) {
1025
if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
1026
result.push(new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name as keyof typeof desc.contributes] as T));
1027
}
1028
}
1029
1030
return result;
1031
});
1032
}
1033
1034
public getExtensionsStatus(): { [id: string]: IExtensionsStatus } {
1035
const result: { [id: string]: IExtensionsStatus } = Object.create(null);
1036
if (this._registry) {
1037
const extensions = this._registry.getAllExtensionDescriptions();
1038
for (const extension of extensions) {
1039
const extensionStatus = this._extensionStatus.get(extension.identifier);
1040
result[extension.identifier.value] = {
1041
id: extension.identifier,
1042
messages: extensionStatus?.messages ?? [],
1043
activationStarted: extensionStatus?.activationStarted ?? false,
1044
activationTimes: extensionStatus?.activationTimes ?? undefined,
1045
runtimeErrors: extensionStatus?.runtimeErrors ?? [],
1046
runningLocation: this._runningLocations.getRunningLocation(extension.identifier),
1047
};
1048
}
1049
}
1050
return result;
1051
}
1052
1053
public async getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]> {
1054
const result = await Promise.all(
1055
this._getExtensionHostManagers(extensionHostKind).map(async extHost => {
1056
let portInfo = await extHost.getInspectPort(tryEnableInspector);
1057
if (portInfo !== undefined) {
1058
portInfo = { ...portInfo, devtoolsLabel: extHost.friendyName };
1059
}
1060
return portInfo;
1061
})
1062
);
1063
// remove 0s:
1064
return result.filter(isDefined);
1065
}
1066
1067
public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void> {
1068
await this._extensionHostManagers
1069
.map(manager => manager.setRemoteEnvironment(env));
1070
}
1071
1072
//#endregion
1073
1074
// --- impl
1075
1076
private _safeInvokeIsEnabled(extension: IExtension): boolean {
1077
try {
1078
return this._extensionEnablementService.isEnabled(extension);
1079
} catch (err) {
1080
return false;
1081
}
1082
}
1083
1084
private _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[], onlyResolverExtensionPoints: boolean): void {
1085
const affectedExtensionPoints: { [extPointName: string]: boolean } = Object.create(null);
1086
for (const extensionDescription of affectedExtensions) {
1087
if (extensionDescription.contributes) {
1088
for (const extPointName in extensionDescription.contributes) {
1089
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
1090
affectedExtensionPoints[extPointName] = true;
1091
}
1092
}
1093
}
1094
}
1095
1096
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
1097
const availableExtensions = this._registry.getAllExtensionDescriptions();
1098
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
1099
perf.mark(onlyResolverExtensionPoints ? 'code/willHandleResolverExtensionPoints' : 'code/willHandleExtensionPoints');
1100
for (const extensionPoint of extensionPoints) {
1101
if (affectedExtensionPoints[extensionPoint.name] && (!onlyResolverExtensionPoints || extensionPoint.canHandleResolver)) {
1102
perf.mark(`code/willHandleExtensionPoint/${extensionPoint.name}`);
1103
AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler);
1104
perf.mark(`code/didHandleExtensionPoint/${extensionPoint.name}`);
1105
}
1106
}
1107
perf.mark(onlyResolverExtensionPoints ? 'code/didHandleResolverExtensionPoints' : 'code/didHandleExtensionPoints');
1108
}
1109
1110
private _getOrCreateExtensionStatus(extensionId: ExtensionIdentifier): ExtensionStatus {
1111
if (!this._extensionStatus.has(extensionId)) {
1112
this._extensionStatus.set(extensionId, new ExtensionStatus(extensionId));
1113
}
1114
return this._extensionStatus.get(extensionId)!;
1115
}
1116
1117
private _handleExtensionPointMessage(msg: IMessage) {
1118
const extensionStatus = this._getOrCreateExtensionStatus(msg.extensionId);
1119
extensionStatus.addMessage(msg);
1120
1121
const extension = this._registry.getExtensionDescription(msg.extensionId);
1122
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
1123
1124
if (msg.type === Severity.Error) {
1125
if (extension && extension.isUnderDevelopment) {
1126
// This message is about the extension currently being developed
1127
this._notificationService.notify({ severity: Severity.Error, message: strMsg });
1128
}
1129
this._logService.error(strMsg);
1130
} else if (msg.type === Severity.Warning) {
1131
if (extension && extension.isUnderDevelopment) {
1132
// This message is about the extension currently being developed
1133
this._notificationService.notify({ severity: Severity.Warning, message: strMsg });
1134
}
1135
this._logService.warn(strMsg);
1136
} else {
1137
this._logService.info(strMsg);
1138
}
1139
1140
if (msg.extensionId && this._environmentService.isBuilt && !this._environmentService.isExtensionDevelopment) {
1141
const { type, extensionId, extensionPointId, message } = msg;
1142
type ExtensionsMessageClassification = {
1143
owner: 'alexdima';
1144
comment: 'A validation message for an extension';
1145
type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Severity of problem.' };
1146
extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension that has a problem.' };
1147
extensionPointId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The extension point that has a problem.' };
1148
message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The message of the problem.' };
1149
};
1150
type ExtensionsMessageEvent = {
1151
type: Severity;
1152
extensionId: string;
1153
extensionPointId: string;
1154
message: string;
1155
};
1156
this._telemetryService.publicLog2<ExtensionsMessageEvent, ExtensionsMessageClassification>('extensionsMessage', {
1157
type, extensionId: extensionId.value, extensionPointId, message
1158
});
1159
}
1160
}
1161
1162
private static _handleExtensionPoint<T extends IExtensionContributions[keyof IExtensionContributions]>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
1163
const users: IExtensionPointUser<T>[] = [];
1164
for (const desc of availableExtensions) {
1165
if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) {
1166
users.push({
1167
description: desc,
1168
value: desc.contributes[extensionPoint.name as keyof typeof desc.contributes] as T,
1169
collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name)
1170
});
1171
}
1172
}
1173
extensionPoint.acceptUsers(users);
1174
}
1175
1176
//#region Called by extension host
1177
1178
private _acquireInternalAPI(extensionHost: IExtensionHost): IInternalExtensionService {
1179
return {
1180
_activateById: (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> => {
1181
return this._activateById(extensionId, reason);
1182
},
1183
_onWillActivateExtension: (extensionId: ExtensionIdentifier): void => {
1184
return this._onWillActivateExtension(extensionId, extensionHost.runningLocation);
1185
},
1186
_onDidActivateExtension: (extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void => {
1187
return this._onDidActivateExtension(extensionId, codeLoadingTime, activateCallTime, activateResolvedTime, activationReason);
1188
},
1189
_onDidActivateExtensionError: (extensionId: ExtensionIdentifier, error: Error): void => {
1190
return this._onDidActivateExtensionError(extensionId, error);
1191
},
1192
_onExtensionRuntimeError: (extensionId: ExtensionIdentifier, err: Error): void => {
1193
return this._onExtensionRuntimeError(extensionId, err);
1194
}
1195
};
1196
}
1197
1198
public async _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
1199
const results = await Promise.all(
1200
this._extensionHostManagers.map(manager => manager.activate(extensionId, reason))
1201
);
1202
const activated = results.some(e => e);
1203
if (!activated) {
1204
throw new Error(`Unknown extension ${extensionId.value}`);
1205
}
1206
}
1207
1208
private _onWillActivateExtension(extensionId: ExtensionIdentifier, runningLocation: ExtensionRunningLocation): void {
1209
this._runningLocations.set(extensionId, runningLocation);
1210
const extensionStatus = this._getOrCreateExtensionStatus(extensionId);
1211
extensionStatus.onWillActivate();
1212
}
1213
1214
private _onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void {
1215
const extensionStatus = this._getOrCreateExtensionStatus(extensionId);
1216
extensionStatus.setActivationTimes(new ActivationTimes(codeLoadingTime, activateCallTime, activateResolvedTime, activationReason));
1217
this._onDidChangeExtensionsStatus.fire([extensionId]);
1218
}
1219
1220
private _onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void {
1221
type ExtensionActivationErrorClassification = {
1222
owner: 'alexdima';
1223
comment: 'An extension failed to activate';
1224
extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension.' };
1225
error: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' };
1226
};
1227
type ExtensionActivationErrorEvent = {
1228
extensionId: string;
1229
error: string;
1230
};
1231
this._telemetryService.publicLog2<ExtensionActivationErrorEvent, ExtensionActivationErrorClassification>('extensionActivationError', {
1232
extensionId: extensionId.value,
1233
error: error.message
1234
});
1235
}
1236
1237
private _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
1238
const extensionStatus = this._getOrCreateExtensionStatus(extensionId);
1239
extensionStatus.addRuntimeError(err);
1240
this._onDidChangeExtensionsStatus.fire([extensionId]);
1241
}
1242
1243
//#endregion
1244
1245
protected abstract _resolveExtensions(): AsyncIterable<ResolvedExtensions>;
1246
protected abstract _onExtensionHostExit(code: number): Promise<void>;
1247
protected abstract _resolveAuthority(remoteAuthority: string): Promise<ResolverResult>;
1248
}
1249
1250
class ExtensionHostCollection extends Disposable {
1251
1252
private _extensionHostManagers: ExtensionHostManagerData[] = [];
1253
1254
public override dispose() {
1255
for (let i = this._extensionHostManagers.length - 1; i >= 0; i--) {
1256
const manager = this._extensionHostManagers[i];
1257
manager.extensionHost.disconnect();
1258
manager.dispose();
1259
}
1260
this._extensionHostManagers = [];
1261
super.dispose();
1262
}
1263
1264
public add(extensionHostManager: IExtensionHostManager, disposableStore: DisposableStore): void {
1265
this._extensionHostManagers.push(new ExtensionHostManagerData(extensionHostManager, disposableStore));
1266
}
1267
1268
public async stopAllInReverse(): Promise<void> {
1269
// See https://github.com/microsoft/vscode/issues/152204
1270
// Dispose extension hosts in reverse creation order because the local extension host
1271
// might be critical in sustaining a connection to the remote extension host
1272
for (let i = this._extensionHostManagers.length - 1; i >= 0; i--) {
1273
const manager = this._extensionHostManagers[i];
1274
await manager.extensionHost.disconnect();
1275
manager.dispose();
1276
}
1277
this._extensionHostManagers = [];
1278
}
1279
1280
public async stopOne(extensionHostManager: IExtensionHostManager): Promise<void> {
1281
const index = this._extensionHostManagers.findIndex(el => el.extensionHost === extensionHostManager);
1282
if (index >= 0) {
1283
this._extensionHostManagers.splice(index, 1);
1284
await extensionHostManager.disconnect();
1285
extensionHostManager.dispose();
1286
}
1287
}
1288
1289
public getByKind(kind: ExtensionHostKind): IExtensionHostManager[] {
1290
return this.filter(el => el.kind === kind);
1291
}
1292
1293
public getByRunningLocation(runningLocation: ExtensionRunningLocation): IExtensionHostManager | null {
1294
for (const el of this._extensionHostManagers) {
1295
if (el.extensionHost.representsRunningLocation(runningLocation)) {
1296
return el.extensionHost;
1297
}
1298
}
1299
return null;
1300
}
1301
1302
*[Symbol.iterator]() {
1303
for (const extensionHostManager of this._extensionHostManagers) {
1304
yield extensionHostManager.extensionHost;
1305
}
1306
}
1307
1308
public map<T>(callback: (extHostManager: IExtensionHostManager) => T): T[] {
1309
return this._extensionHostManagers.map(el => callback(el.extensionHost));
1310
}
1311
1312
public every(callback: (extHostManager: IExtensionHostManager) => unknown): boolean {
1313
return this._extensionHostManagers.every(el => callback(el.extensionHost));
1314
}
1315
1316
public filter(callback: (extHostManager: IExtensionHostManager) => unknown): IExtensionHostManager[] {
1317
return this._extensionHostManagers.filter(el => callback(el.extensionHost)).map(el => el.extensionHost);
1318
}
1319
}
1320
1321
class ExtensionHostManagerData {
1322
constructor(
1323
public readonly extensionHost: IExtensionHostManager,
1324
public readonly disposableStore: DisposableStore
1325
) { }
1326
1327
public dispose(): void {
1328
this.disposableStore.dispose();
1329
this.extensionHost.dispose();
1330
}
1331
}
1332
1333
export class ResolverExtensions {
1334
constructor(
1335
public readonly extensions: IExtensionDescription[],
1336
) { }
1337
}
1338
1339
export class LocalExtensions {
1340
constructor(
1341
public readonly extensions: IExtensionDescription[],
1342
) { }
1343
}
1344
1345
export class RemoteExtensions {
1346
constructor(
1347
public readonly extensions: IExtensionDescription[],
1348
) { }
1349
}
1350
1351
export type ResolvedExtensions = ResolverExtensions | LocalExtensions | RemoteExtensions;
1352
1353
export interface IExtensionHostFactory {
1354
createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null;
1355
}
1356
1357
class DeltaExtensionsQueueItem {
1358
constructor(
1359
public readonly toAdd: IExtension[],
1360
public readonly toRemove: string[] | IExtension[]
1361
) { }
1362
}
1363
1364
export function isResolverExtension(extension: IExtensionDescription): boolean {
1365
return !!extension.activationEvents?.some(activationEvent => activationEvent.startsWith('onResolveRemoteAuthority:'));
1366
}
1367
1368
/**
1369
* @argument extensions The extensions to be checked.
1370
* @argument ignoreWorkspaceTrust Do not take workspace trust into account.
1371
*/
1372
export function checkEnabledAndProposedAPI(logService: ILogService, extensionEnablementService: IWorkbenchExtensionEnablementService, extensionsProposedApi: ExtensionsProposedApi, extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] {
1373
// enable or disable proposed API per extension
1374
extensionsProposedApi.updateEnabledApiProposals(extensions);
1375
1376
// keep only enabled extensions
1377
return filterEnabledExtensions(logService, extensionEnablementService, extensions, ignoreWorkspaceTrust);
1378
}
1379
1380
/**
1381
* Return the subset of extensions that are enabled.
1382
* @argument ignoreWorkspaceTrust Do not take workspace trust into account.
1383
*/
1384
export function filterEnabledExtensions(logService: ILogService, extensionEnablementService: IWorkbenchExtensionEnablementService, extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] {
1385
const enabledExtensions: IExtensionDescription[] = [], extensionsToCheck: IExtensionDescription[] = [], mappedExtensions: IExtension[] = [];
1386
for (const extension of extensions) {
1387
if (extension.isUnderDevelopment) {
1388
// Never disable extensions under development
1389
enabledExtensions.push(extension);
1390
} else {
1391
extensionsToCheck.push(extension);
1392
mappedExtensions.push(toExtension(extension));
1393
}
1394
}
1395
1396
const enablementStates = extensionEnablementService.getEnablementStates(mappedExtensions, ignoreWorkspaceTrust ? { trusted: true } : undefined);
1397
for (let index = 0; index < enablementStates.length; index++) {
1398
if (extensionEnablementService.isEnabledEnablementState(enablementStates[index])) {
1399
enabledExtensions.push(extensionsToCheck[index]);
1400
} else {
1401
if (isCI) {
1402
logService.info(`filterEnabledExtensions: extension '${extensionsToCheck[index].identifier.value}' is disabled`);
1403
}
1404
}
1405
}
1406
1407
return enabledExtensions;
1408
}
1409
1410
/**
1411
* @argument extension The extension to be checked.
1412
* @argument ignoreWorkspaceTrust Do not take workspace trust into account.
1413
*/
1414
export function extensionIsEnabled(logService: ILogService, extensionEnablementService: IWorkbenchExtensionEnablementService, extension: IExtensionDescription, ignoreWorkspaceTrust: boolean): boolean {
1415
return filterEnabledExtensions(logService, extensionEnablementService, [extension], ignoreWorkspaceTrust).includes(extension);
1416
}
1417
1418
function includes(extensions: IExtensionDescription[], identifier: ExtensionIdentifier): boolean {
1419
for (const extension of extensions) {
1420
if (ExtensionIdentifier.equals(extension.identifier, identifier)) {
1421
return true;
1422
}
1423
}
1424
return false;
1425
}
1426
1427
export class ExtensionStatus {
1428
1429
private readonly _messages: IMessage[] = [];
1430
public get messages(): IMessage[] {
1431
return this._messages;
1432
}
1433
1434
private _activationTimes: ActivationTimes | null = null;
1435
public get activationTimes(): ActivationTimes | null {
1436
return this._activationTimes;
1437
}
1438
1439
private _runtimeErrors: Error[] = [];
1440
public get runtimeErrors(): Error[] {
1441
return this._runtimeErrors;
1442
}
1443
1444
private _activationStarted: boolean = false;
1445
public get activationStarted(): boolean {
1446
return this._activationStarted;
1447
}
1448
1449
constructor(
1450
public readonly id: ExtensionIdentifier,
1451
) { }
1452
1453
public clearRuntimeStatus(): void {
1454
this._activationStarted = false;
1455
this._activationTimes = null;
1456
this._runtimeErrors = [];
1457
}
1458
1459
public addMessage(msg: IMessage): void {
1460
this._messages.push(msg);
1461
}
1462
1463
public setActivationTimes(activationTimes: ActivationTimes) {
1464
this._activationTimes = activationTimes;
1465
}
1466
1467
public addRuntimeError(err: Error): void {
1468
this._runtimeErrors.push(err);
1469
}
1470
1471
public onWillActivate() {
1472
this._activationStarted = true;
1473
}
1474
}
1475
1476
interface IExtensionHostCrashInfo {
1477
timestamp: number;
1478
}
1479
1480
export class ExtensionHostCrashTracker {
1481
1482
private static _TIME_LIMIT = 5 * 60 * 1000; // 5 minutes
1483
private static _CRASH_LIMIT = 3;
1484
1485
private readonly _recentCrashes: IExtensionHostCrashInfo[] = [];
1486
1487
private _removeOldCrashes(): void {
1488
const limit = Date.now() - ExtensionHostCrashTracker._TIME_LIMIT;
1489
while (this._recentCrashes.length > 0 && this._recentCrashes[0].timestamp < limit) {
1490
this._recentCrashes.shift();
1491
}
1492
}
1493
1494
public registerCrash(): void {
1495
this._removeOldCrashes();
1496
this._recentCrashes.push({ timestamp: Date.now() });
1497
}
1498
1499
public shouldAutomaticallyRestart(): boolean {
1500
this._removeOldCrashes();
1501
return (this._recentCrashes.length < ExtensionHostCrashTracker._CRASH_LIMIT);
1502
}
1503
}
1504
1505
/**
1506
* This can run correctly only on the renderer process because that is the only place
1507
* where all extension points and all implicit activation events generators are known.
1508
*/
1509
export class ImplicitActivationAwareReader implements IActivationEventsReader {
1510
public readActivationEvents(extensionDescription: IExtensionDescription): string[] {
1511
return ImplicitActivationEvents.readActivationEvents(extensionDescription);
1512
}
1513
}
1514
1515
class ActivationFeatureMarkdowneRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer {
1516
1517
readonly type = 'markdown';
1518
1519
shouldRender(manifest: IExtensionManifest): boolean {
1520
return !!manifest.activationEvents;
1521
}
1522
1523
render(manifest: IExtensionManifest): IRenderedData<IMarkdownString> {
1524
const activationEvents = manifest.activationEvents || [];
1525
const data = new MarkdownString();
1526
if (activationEvents.length) {
1527
for (const activationEvent of activationEvents) {
1528
data.appendMarkdown(`- \`${activationEvent}\`\n`);
1529
}
1530
}
1531
return {
1532
data,
1533
dispose: () => { }
1534
};
1535
}
1536
}
1537
1538
Registry.as<IExtensionFeaturesRegistry>(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({
1539
id: 'activationEvents',
1540
label: nls.localize('activation', "Activation Events"),
1541
access: {
1542
canToggle: false
1543
},
1544
renderer: new SyncDescriptor(ActivationFeatureMarkdowneRenderer),
1545
});
1546
1547