Improve handling of extension descriptions (#193145)

This commit is contained in:
Alexandru Dima
2023-09-14 16:17:42 -07:00
committed by GitHub
parent 9c12e02b7b
commit bccfade64a
12 changed files with 155 additions and 126 deletions
@@ -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)
@@ -194,7 +194,7 @@ export class ExtensionHostMain {
}
private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData {
initData.allExtensions.forEach((ext) => {
initData.extensions.allExtensions.forEach((ext) => {
(<Mutable<IRelaxedExtensionDescription>>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation));
});
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
@@ -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<IExtensionDescription[]>,
private readonly _getExtensions: () => Promise<IExtensionDescription[]>,
private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise<ExtensionDescriptionRegistrySnapshot>,
@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<IRemoteExtensionHostInitData> => {
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;
}
};
}
@@ -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<IExtensionHostInitData> {
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,
@@ -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((<IExtensionDescription[]>[]).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<void> {
private async _updateExtensionsOnExtHosts(versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
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<ExtensionRunningLocation | null>): Promise<void> {
private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[], removedRunningLocation: ExtensionIdentifierMap<ExtensionRunningLocation | null>): Promise<void> {
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<IExtensionDescription[]> {
return this._installedExtensionsReady.wait().then(() => this.extensions);
protected _getExtensionRegistrySnapshotWhenReady(): Promise<ExtensionDescriptionRegistrySnapshot> {
return this._installedExtensionsReady.wait().then(() => this._registry.getSnapshot());
}
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
@@ -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<void>();
public readonly onDidChange = this._onDidChange.event;
private _versionId: number = 0;
private _extensionDescriptions: IExtensionDescription[];
private _extensionsMap!: ExtensionIdentifierMap<IExtensionDescription>;
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);
}
@@ -53,7 +53,7 @@ export interface IExtensionHostManager {
* Returns `null` if no resolver for `remoteAuthority` is found.
*/
getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null>;
start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void>;
start(extensionRegistryVersionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void>;
extensionTestsExecute(): Promise<number>;
setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
}
@@ -436,12 +436,12 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
return proxy.getCanonicalURI(remoteAuthority, uri);
}
public async start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void> {
public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void> {
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<void> {
public async deltaExtensions(incomingExtensionsDelta: IExtensionDescriptionDelta): Promise<void> {
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<void> {
@@ -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<boolean> {
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<void> {
public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void> {
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<number> {
@@ -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;
@@ -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<IMessagePassingProtocol>;
@@ -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 {
@@ -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<IExtensionHostInitData> {
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,
@@ -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<IExtensionHostInitData> {
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,
@@ -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<IExtensionDescription[]>,
private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise<ExtensionDescriptionRegistrySnapshot>,
@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<IWebWorkerExtensionHostInitData> => {
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<IRemoteExtensionHostInitData> => {
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;
}
};
}