From bccfade64adb249f57c8fcf03cba41609f76ce5c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 15 Sep 2023 01:17:42 +0200 Subject: [PATCH] Improve handling of extension descriptions (#193145) --- .../api/common/extHostExtensionService.ts | 6 +-- .../workbench/api/common/extensionHostMain.ts | 2 +- .../extensions/browser/extensionService.ts | 39 ++++++-------- .../browser/webWorkerExtensionHost.ts | 14 ++--- .../common/abstractExtensionService.ts | 22 ++++---- .../common/extensionDescriptionRegistry.ts | 28 +++++++++- .../extensions/common/extensionHostManager.ts | 32 ++++++------ .../common/extensionHostProtocol.ts | 12 +++-- .../services/extensions/common/extensions.ts | 52 +++++++++++++------ .../extensions/common/remoteExtensionHost.ts | 12 ++--- .../localProcessExtensionHost.ts | 12 ++--- .../nativeExtensionService.ts | 50 ++++++++---------- 12 files changed, 155 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 99fccebaee0..404f8f7d9c0 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -157,9 +157,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._readyToStartExtensionHost = new Barrier(); this._readyToRunExtensions = new Barrier(); this._eagerExtensionsActivated = new Barrier(); - this._activationEventsReader = new SyncedActivationEventsReader(this._initData.activationEvents); - this._globalRegistry = new ExtensionDescriptionRegistry(this._activationEventsReader, this._initData.allExtensions); - const myExtensionsSet = new ExtensionIdentifierSet(this._initData.myExtensions); + this._activationEventsReader = new SyncedActivationEventsReader(this._initData.extensions.activationEvents); + this._globalRegistry = new ExtensionDescriptionRegistry(this._activationEventsReader, this._initData.extensions.allExtensions); + const myExtensionsSet = new ExtensionIdentifierSet(this._initData.extensions.myExtensions); this._myRegistry = new ExtensionDescriptionRegistry( this._activationEventsReader, filterExtensions(this._globalRegistry, myExtensionsSet) diff --git a/src/vs/workbench/api/common/extensionHostMain.ts b/src/vs/workbench/api/common/extensionHostMain.ts index 40f1cdaf146..2bd275cbc21 100644 --- a/src/vs/workbench/api/common/extensionHostMain.ts +++ b/src/vs/workbench/api/common/extensionHostMain.ts @@ -194,7 +194,7 @@ export class ExtensionHostMain { } private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData { - initData.allExtensions.forEach((ext) => { + initData.extensions.allExtensions.forEach((ext) => { (>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); }); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 0317de83928..25885c7c4f9 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -26,14 +26,15 @@ import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWo import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider'; import { AbstractExtensionService, IExtensionHostFactory, ResolvedExtensions, checkEnabledAndProposedAPI } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker'; -import { ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi'; import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; -import { IRemoteExtensionHostDataProvider, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; +import { IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; @@ -70,7 +71,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const extensionHostFactory = new BrowserExtensionHostFactory( extensionsProposedApi, () => this._scanWebExtensions(), - () => this._getExtensions(), + () => this._getExtensionRegistrySnapshotWhenReady(), instantiationService, remoteAgentService, remoteAuthorityResolverService, @@ -216,7 +217,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { constructor( private readonly _extensionsProposedApi: ExtensionsProposedApi, private readonly _scanWebExtensions: () => Promise, - private readonly _getExtensions: () => Promise, + private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -255,18 +256,14 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true); const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false); const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); - return { - allExtensions: localExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const extensions = new ExtensionHostExtensions(0, localExtensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } else { // restart case - const allExtensions = await this._getExtensions(); - const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation); - return { - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); + const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } } }; @@ -275,28 +272,26 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { private _createRemoteExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, remoteAuthority: string): IRemoteExtensionHostDataProvider { return { remoteAuthority: remoteAuthority, - getInitData: async () => { - const allExtensions = await this._getExtensions(); + getInitData: async (): Promise => { + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); const remoteEnv = await this._remoteAgentService.getEnvironment(); if (!remoteEnv) { throw new Error('Cannot provide init data for remote extension host!'); } - const myExtensions = runningLocations.filterByExtensionHostKind(allExtensions, ExtensionHostKind.Remote); + const myExtensions = runningLocations.filterByExtensionHostKind(snapshot.extensions, ExtensionHostKind.Remote); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); - const initData = { + return { connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority), pid: remoteEnv.pid, appRoot: remoteEnv.appRoot, extensionHostLogsPath: remoteEnv.extensionHostLogsPath, globalStorageHome: remoteEnv.globalStorageHome, workspaceStorageHome: remoteEnv.workspaceStorageHome, - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier), + extensions, }; - - return initData; } }; } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 5ec7f0690c6..a7ce017ab9f 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { parentOriginHash } from 'vs/base/browser/iframe'; import { Barrier } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; @@ -15,7 +16,6 @@ import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; @@ -25,15 +25,13 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { parentOriginHash } from 'vs/base/browser/iframe'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { ExtensionHostExitCode, IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost } from 'vs/workbench/services/extensions/common/extensions'; export interface IWebWorkerExtensionHostInitData { - readonly allExtensions: IExtensionDescription[]; - readonly myExtensions: ExtensionIdentifier[]; + readonly extensions: ExtensionHostExtensions; } export interface IWebWorkerExtensionHostDataProvider { @@ -43,7 +41,7 @@ export interface IWebWorkerExtensionHostDataProvider { export class WebWorkerExtensionHost extends Disposable implements IExtensionHost { public readonly remoteAuthority = null; - public readonly extensions = new ExtensionHostExtensions(); + public extensions: ExtensionHostExtensions | null = null; private readonly _onDidExit = this._register(new Emitter<[number, string | null]>()); public readonly onExit: Event<[number, string | null]> = this._onDidExit.event; @@ -267,8 +265,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost private async _createExtHostInitData(): Promise { const initData = await this._initDataProvider.getInitData(); + this.extensions = initData.extensions; const workspace = this._contextService.getWorkspace(); - const deltaExtensions = this.extensions.set(initData.allExtensions, initData.myExtensions); const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; let nlsUrlWithDetails: URI | undefined = undefined; // Only use the nlsBaseUrl if we are using a language other than the default, English. @@ -304,9 +302,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost includeStack: false, logNative: this._environmentService.debugRenderer }, - allExtensions: deltaExtensions.toAdd, - activationEvents: deltaExtensions.addActivationEvents, - myExtensions: deltaExtensions.myToAdd, + extensions: this.extensions.toSnapshot(), nlsBaseUrl: nlsUrlWithDetails, telemetryInfo: { sessionId: this._telemetryService.sessionId, diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index ccb729ce206..e1308458a40 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -31,7 +31,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionDescriptionRegistryLock, IActivationEventsReader, LockableExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { ExtensionDescriptionRegistryLock, ExtensionDescriptionRegistrySnapshot, IActivationEventsReader, LockableExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionHostManager, createExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -298,22 +298,22 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._doHandleExtensionPoints(([]).concat(toAdd).concat(toRemove)); // Update the extension host - await this._updateExtensionsOnExtHosts(toAdd, toRemove.map(e => e.identifier)); + await this._updateExtensionsOnExtHosts(result.versionId, toAdd, toRemove.map(e => e.identifier)); for (let i = 0; i < toAdd.length; i++) { this._activateAddedExtensionIfNeeded(toAdd[i]); } } - private async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { + private async _updateExtensionsOnExtHosts(versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { const removedRunningLocation = this._runningLocations.deltaExtensions(toAdd, toRemove); const promises = this._extensionHostManagers.map( - extHostManager => this._updateExtensionsOnExtHost(extHostManager, toAdd, toRemove, removedRunningLocation) + extHostManager => this._updateExtensionsOnExtHost(extHostManager, versionId, toAdd, toRemove, removedRunningLocation) ); await Promise.all(promises); } - private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[], removedRunningLocation: ExtensionIdentifierMap): Promise { + private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[], removedRunningLocation: ExtensionIdentifierMap): Promise { const myToAdd = this._runningLocations.filterByExtensionHostManager(toAdd, extensionHostManager); const myToRemove = filterExtensionIdentifiers(toRemove, removedRunningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation)); const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd); @@ -322,7 +322,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const printIds = (extensions: ExtensionIdentifier[]) => extensions.map(e => e.value).join(','); this._logService.info(`AbstractExtensionService: Calling deltaExtensions: toRemove: [${printIds(toRemove)}], toAdd: [${printExtIds(toAdd)}], myToRemove: [${printIds(myToRemove)}], myToAdd: [${printExtIds(myToAdd)}],`); } - await extensionHostManager.deltaExtensions({ toRemove, toAdd, addActivationEvents, myToRemove, myToAdd: myToAdd.map(extension => extension.identifier) }); + await extensionHostManager.deltaExtensions({ versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd: myToAdd.map(extension => extension.identifier) }); } public canAddExtension(extension: IExtensionDescription): boolean { @@ -440,11 +440,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._processExtensions(lock, resolvedExtensions); // Start extension hosts which are not automatically started - const allExtensions = this._registry.getAllExtensionDescriptions(); + const snapshot = this._registry.getSnapshot(); for (const extHostManager of this._extensionHostManagers) { if (extHostManager.startup !== ExtensionHostStartup.EagerAutoStart) { - const extensions = this._runningLocations.filterByExtensionHostManager(allExtensions, extHostManager); - extHostManager.start(allExtensions, extensions.map(extension => extension.identifier)); + const extensions = this._runningLocations.filterByExtensionHostManager(snapshot.extensions, extHostManager); + extHostManager.start(snapshot.versionId, snapshot.extensions, extensions.map(extension => extension.identifier)); } } } finally { @@ -934,8 +934,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return this._registry.getAllExtensionDescriptions(); } - protected _getExtensions(): Promise { - return this._installedExtensionsReady.wait().then(() => this.extensions); + protected _getExtensionRegistrySnapshotWhenReady(): Promise { + return this._installedExtensionsReady.wait().then(() => this._registry.getSnapshot()); } public getExtension(id: string): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 16365d94990..96b940c9ef0 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -10,6 +10,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' export class DeltaExtensionsResult { constructor( + public readonly versionId: number, public readonly removedDueToLooping: IExtensionDescription[] ) { } } @@ -45,6 +46,7 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti private readonly _onDidChange = new Emitter(); public readonly onDidChange = this._onDidChange.event; + private _versionId: number = 0; private _extensionDescriptions: IExtensionDescription[]; private _extensionsMap!: ExtensionIdentifierMap; private _extensionsArr!: IExtensionDescription[]; @@ -93,10 +95,14 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti } } - public set(extensionDescriptions: IExtensionDescription[]): void { + public set(extensionDescriptions: IExtensionDescription[]): { versionId: number } { this._extensionDescriptions = extensionDescriptions; this._initialize(); + this._versionId++; this._onDidChange.fire(undefined); + return { + versionId: this._versionId + }; } public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult { @@ -112,8 +118,9 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti this._extensionDescriptions = removeExtensions(this._extensionDescriptions, looping.map(ext => ext.identifier)); this._initialize(); + this._versionId++; this._onDidChange.fire(undefined); - return new DeltaExtensionsResult(looping); + return new DeltaExtensionsResult(this._versionId, looping); } private static _findLoopingExtensions(extensionDescriptions: IExtensionDescription[]): IExtensionDescription[] { @@ -217,6 +224,13 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti return this._extensionsArr.slice(0); } + public getSnapshot(): ExtensionDescriptionRegistrySnapshot { + return new ExtensionDescriptionRegistrySnapshot( + this._versionId, + this.getAllExtensionDescriptions() + ); + } + public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined { const extension = this._extensionsMap.get(extensionId); return extension ? extension : undefined; @@ -239,6 +253,13 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti } } +export class ExtensionDescriptionRegistrySnapshot { + constructor( + public readonly versionId: number, + public readonly extensions: readonly IExtensionDescription[] + ) { } +} + export interface IActivationEventsReader { readActivationEvents(extensionDescription: IExtensionDescription): string[] | undefined; } @@ -282,6 +303,9 @@ export class LockableExtensionDescriptionRegistry implements IReadOnlyExtensionD public getAllExtensionDescriptions(): IExtensionDescription[] { return this._actual.getAllExtensionDescriptions(); } + public getSnapshot(): ExtensionDescriptionRegistrySnapshot { + return this._actual.getSnapshot(); + } public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined { return this._actual.getExtensionDescription(extensionId); } diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 59c293fa8b1..0980a41d10c 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -53,7 +53,7 @@ export interface IExtensionHostManager { * Returns `null` if no resolver for `remoteAuthority` is found. */ getCanonicalURI(remoteAuthority: string, uri: URI): Promise; - start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise; + start(extensionRegistryVersionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise; extensionTestsExecute(): Promise; setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } @@ -436,12 +436,12 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { return proxy.getCanonicalURI(remoteAuthority, uri); } - public async start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { + public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { const proxy = await this._proxy; if (!proxy) { return; } - const deltaExtensions = this._extensionHost.extensions.set(allExtensions, myExtensions); + const deltaExtensions = this._extensionHost.extensions!.set(extensionRegistryVersionId, allExtensions, myExtensions); return proxy.startExtensionHost(deltaExtensions); } @@ -457,17 +457,21 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { return this._extensionHost.runningLocation.equals(runningLocation); } - public async deltaExtensions(extensionsDelta: IExtensionDescriptionDelta): Promise { + public async deltaExtensions(incomingExtensionsDelta: IExtensionDescriptionDelta): Promise { const proxy = await this._proxy; if (!proxy) { return; } - this._extensionHost.extensions.delta(extensionsDelta); - return proxy.deltaExtensions(extensionsDelta); + const outgoingExtensionsDelta = this._extensionHost.extensions!.delta(incomingExtensionsDelta); + if (!outgoingExtensionsDelta) { + // The extension host already has this version of the extensions. + return; + } + return proxy.deltaExtensions(outgoingExtensionsDelta); } public containsExtension(extensionId: ExtensionIdentifier): boolean { - return this._extensionHost.extensions.containsExtension(extensionId); + return this._extensionHost.extensions?.containsExtension(extensionId) ?? false; } public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { @@ -529,7 +533,7 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos return this._actual; } const actual = this._createActual(reason); - await actual.start([], []); + await actual.start(this._lazyStartExtensions!.versionId, this._lazyStartExtensions!.allExtensions, this._lazyStartExtensions!.myExtensions); return actual; } @@ -550,13 +554,12 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos this._lazyStartExtensions!.delta(extensionsDelta); if (extensionsDelta.myToAdd.length > 0) { const actual = this._createActual(`contains ${extensionsDelta.myToAdd.length} new extension(s) (installed or enabled): ${extensionsDelta.myToAdd.map(extId => extId.value)}`); - const { toAdd, myToAdd } = this._lazyStartExtensions!.toDelta(); - actual.start(toAdd, myToAdd); + await actual.start(this._lazyStartExtensions!.versionId, this._lazyStartExtensions!.allExtensions, this._lazyStartExtensions!.myExtensions); return; } } public containsExtension(extensionId: ExtensionIdentifier): boolean { - return this._extensionHost.extensions.containsExtension(extensionId); + return this._extensionHost.extensions?.containsExtension(extensionId) ?? false; } public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { await this._startCalled.wait(); @@ -615,17 +618,16 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos } throw new Error(`Cannot resolve canonical URI`); } - public async start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { + public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { if (myExtensions.length > 0) { // there are actual extensions, so let's launch the extension host const actual = this._createActual(`contains ${myExtensions.length} extension(s): ${myExtensions.map(extId => extId.value)}.`); - const result = actual.start(allExtensions, myExtensions); + const result = actual.start(extensionRegistryVersionId, allExtensions, myExtensions); this._startCalled.open(); return result; } // there are no actual extensions running, store extensions in `this._lazyStartExtensions` - this._lazyStartExtensions = new ExtensionHostExtensions(); - this._lazyStartExtensions.set(allExtensions, myExtensions); + this._lazyStartExtensions = new ExtensionHostExtensions(extensionRegistryVersionId, allExtensions, myExtensions); this._startCalled.open(); } public async extensionTestsExecute(): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 4472c9b7bdb..87b031a1c56 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -9,7 +9,15 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; +export interface IExtensionDescriptionSnapshot { + readonly versionId: number; + readonly allExtensions: IExtensionDescription[]; + readonly activationEvents: { [extensionId: string]: string[] }; + readonly myExtensions: ExtensionIdentifier[]; +} + export interface IExtensionDescriptionDelta { + readonly versionId: number; readonly toRemove: ExtensionIdentifier[]; readonly toAdd: IExtensionDescription[]; readonly addActivationEvents: { [extensionId: string]: string[] }; @@ -27,9 +35,7 @@ export interface IExtensionHostInitData { parentPid: number | 0; environment: IEnvironment; workspace?: IStaticWorkspaceData | null; - activationEvents: { [extensionId: string]: string[] }; - allExtensions: IExtensionDescription[]; - myExtensions: ExtensionIdentifier[]; + extensions: IExtensionDescriptionSnapshot; nlsBaseUrl?: URI; telemetryInfo: { readonly sessionId: string; diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 436f780bd00..65d59f165b5 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -13,7 +13,7 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, Ex import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensionHostKind'; -import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IExtensionDescriptionDelta, IExtensionDescriptionSnapshot } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -120,7 +120,7 @@ export interface IExtensionHost { * extension will execute or is executing on this extension host. * **NOTE**: this will reflect extensions correctly only after `start()` resolves. */ - readonly extensions: ExtensionHostExtensions; + readonly extensions: ExtensionHostExtensions | null; readonly onExit: Event<[number, string | null]>; start(): Promise; @@ -130,26 +130,41 @@ export interface IExtensionHost { } export class ExtensionHostExtensions { - + private _versionId: number; private _allExtensions: IExtensionDescription[]; private _myExtensions: ExtensionIdentifier[]; - constructor() { - this._allExtensions = []; - this._myExtensions = []; + public get versionId(): number { + return this._versionId; } - public toDelta(): IExtensionDescriptionDelta { + public get allExtensions(): IExtensionDescription[] { + return this._allExtensions; + } + + public get myExtensions(): ExtensionIdentifier[] { + return this._myExtensions; + } + + constructor(versionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]) { + this._versionId = versionId; + this._allExtensions = allExtensions.slice(0); + this._myExtensions = myExtensions.slice(0); + } + + toSnapshot(): IExtensionDescriptionSnapshot { return { - toRemove: [], - toAdd: this._allExtensions, - addActivationEvents: ImplicitActivationEvents.createActivationEventsMap(this._allExtensions), - myToRemove: [], - myToAdd: this._myExtensions + versionId: this._versionId, + allExtensions: this._allExtensions, + myExtensions: this._myExtensions, + activationEvents: ImplicitActivationEvents.createActivationEventsMap(this._allExtensions) }; } - public set(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta { + public set(versionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta { + if (this._versionId > versionId) { + throw new Error(`ExtensionHostExtensions: invalid versionId ${versionId} (current: ${this._versionId})`); + } const toRemove: ExtensionIdentifier[] = []; const toAdd: IExtensionDescription[] = []; const myToRemove: ExtensionIdentifier[] = []; @@ -210,12 +225,17 @@ export class ExtensionHostExtensions { } const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd); - const delta = { toRemove, toAdd, addActivationEvents, myToRemove, myToAdd }; + const delta = { versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd }; this.delta(delta); return delta; } - public delta(extensionsDelta: IExtensionDescriptionDelta): void { + public delta(extensionsDelta: IExtensionDescriptionDelta): IExtensionDescriptionDelta | null { + if (this._versionId >= extensionsDelta.versionId) { + // ignore older deltas + return null; + } + const { toRemove, toAdd, myToRemove, myToAdd } = extensionsDelta; // First handle removals const toRemoveSet = new ExtensionIdentifierSet(toRemove); @@ -239,6 +259,8 @@ export class ExtensionHostExtensions { for (const extensionId of myToAdd) { this._myExtensions.push(extensionId); } + + return extensionsDelta; } public containsExtension(extensionId: ExtensionIdentifier): boolean { diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 7ac17418689..a4036f94bd2 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -36,8 +35,7 @@ export interface IRemoteExtensionHostInitData { readonly extensionHostLogsPath: URI; readonly globalStorageHome: URI; readonly workspaceStorageHome: URI; - readonly allExtensions: IExtensionDescription[]; - readonly myExtensions: ExtensionIdentifier[]; + readonly extensions: ExtensionHostExtensions; } export interface IRemoteExtensionHostDataProvider { @@ -49,7 +47,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { public readonly remoteAuthority: string; public readonly startup = ExtensionHostStartup.EagerAutoStart; - public readonly extensions = new ExtensionHostExtensions(); + public extensions: ExtensionHostExtensions | null = null; private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); public readonly onExit: Event<[number, string | null]> = this._onExit.event; @@ -203,8 +201,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { private async _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise { const remoteInitData = await this._initDataProvider.getInitData(); + this.extensions = remoteInitData.extensions; const workspace = this._contextService.getWorkspace(); - const deltaExtensions = this.extensions.set(remoteInitData.allExtensions, remoteInitData.myExtensions); return { commit: this._productService.commit, version: this._productService.version, @@ -240,9 +238,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { includeStack: false, logNative: Boolean(this._environmentService.debugExtensionHost.debugId) }, - allExtensions: deltaExtensions.toAdd, - activationEvents: deltaExtensions.addActivationEvents, - myExtensions: deltaExtensions.myToAdd, + extensions: this.extensions.toSnapshot(), telemetryInfo: { sessionId: this._telemetryService.sessionId, machineId: this._telemetryService.machineId, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index b72e19b9ec6..c635003cea3 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -21,7 +21,6 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/common/native'; @@ -42,8 +41,7 @@ import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/life import { parseExtensionDevOptions } from '../common/extensionDevOptions'; export interface ILocalProcessExtensionHostInitData { - readonly allExtensions: IExtensionDescription[]; - readonly myExtensions: ExtensionIdentifier[]; + readonly extensions: ExtensionHostExtensions; } export interface ILocalProcessExtensionHostDataProvider { @@ -93,7 +91,7 @@ export class ExtensionHostProcess { export class NativeLocalProcessExtensionHost implements IExtensionHost { public readonly remoteAuthority = null; - public readonly extensions = new ExtensionHostExtensions(); + public extensions: ExtensionHostExtensions | null = null; private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>(); public readonly onExit: Event<[number, string]> = this._onExit.event; @@ -416,8 +414,8 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { private async _createExtHostInitData(): Promise { const initData = await this._initDataProvider.getInitData(); + this.extensions = initData.extensions; const workspace = this._contextService.getWorkspace(); - const deltaExtensions = this.extensions.set(initData.allExtensions, initData.myExtensions); return { commit: this._productService.commit, version: this._productService.version, @@ -454,9 +452,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { includeStack: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose), logNative: !this._isExtensionDevTestFromCli && this._isExtensionDevHost }, - allExtensions: deltaExtensions.toAdd, - activationEvents: deltaExtensions.addActivationEvents, - myExtensions: deltaExtensions.myToAdd, + extensions: this.extensions.toSnapshot(), telemetryInfo: { sessionId: this._telemetryService.sessionId, machineId: this._telemetryService.machineId, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index d63228db9d2..c5adae5f0d2 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -40,6 +40,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { EnablementState, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { AbstractExtensionService, ExtensionHostCrashTracker, IExtensionHostFactory, ResolvedExtensions, checkEnabledAndProposedAPI, extensionIsEnabled } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -47,9 +48,9 @@ import { ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/e import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker'; -import { ExtensionHostStartup, IExtensionHost, IExtensionService, WebWorkerExtHostConfigValue, toExtension, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionService, WebWorkerExtHostConfigValue, toExtension, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi'; -import { IRemoteExtensionHostDataProvider, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; +import { IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner'; import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData, NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -91,7 +92,7 @@ export class NativeExtensionService extends AbstractExtensionService implements const extensionHostFactory = new NativeExtensionHostFactory( extensionsProposedApi, extensionScanner, - () => this._getExtensions(), + () => this._getExtensionRegistrySnapshotWhenReady(), instantiationService, environmentService, extensionEnablementService, @@ -521,7 +522,7 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { constructor( private readonly _extensionsProposedApi: ExtensionsProposedApi, private readonly _extensionScanner: CachedExtensionScanner, - private readonly _getExtensions: () => Promise, + private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, @@ -581,22 +582,17 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false); const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); + const extensions = new ExtensionHostExtensions(0, localExtensions, myExtensions.map(extension => extension.identifier)); if (isCI) { this._logService.info(`NativeExtensionHostFactory._createLocalProcessExtensionHostDataProvider.myExtensions: ${myExtensions.map(ext => ext.identifier.value).join(',')}`); } - - return { - allExtensions: localExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + return { extensions }; } else { // restart case - const allExtensions = await this._getExtensions(); - const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation); - return { - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); + const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } } }; @@ -605,12 +601,10 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { private _createWebWorkerExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: LocalWebWorkerRunningLocation): IWebWorkerExtensionHostDataProvider { return { getInitData: async (): Promise => { - const allExtensions = await this._getExtensions(); - const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation); - return { - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); + const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } }; } @@ -618,28 +612,26 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { private _createRemoteExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, remoteAuthority: string): IRemoteExtensionHostDataProvider { return { remoteAuthority: remoteAuthority, - getInitData: async () => { - const allExtensions = await this._getExtensions(); + getInitData: async (): Promise => { + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); const remoteEnv = await this._remoteAgentService.getEnvironment(); if (!remoteEnv) { throw new Error('Cannot provide init data for remote extension host!'); } - const myExtensions = runningLocations.filterByExtensionHostKind(allExtensions, ExtensionHostKind.Remote); + const myExtensions = runningLocations.filterByExtensionHostKind(snapshot.extensions, ExtensionHostKind.Remote); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); - const initData = { + return { connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority), pid: remoteEnv.pid, appRoot: remoteEnv.appRoot, extensionHostLogsPath: remoteEnv.extensionHostLogsPath, globalStorageHome: remoteEnv.globalStorageHome, workspaceStorageHome: remoteEnv.workspaceStorageHome, - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier), + extensions, }; - - return initData; } }; }