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