Change:
<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: No <!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --- VS Code Version: 1.65.0 OS Version: Fedora 35 Steps to Reproduce: extension (v11.10.0) vscode, git extension is not initialized and restart, git extension works again With vscode-icons disabled With vscode-icons enabled
Reproduces with the following extension: : :

Discussion:
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionActivationReason, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; <del> const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined); </del> <ins> import { Barrier } from 'vs/base/common/async'; </ins> /** * Represents the source code (module) of an extension.

export class ExtensionsActivator implements IDisposable { <del> private _isDisposed: boolean; </del> private readonly _registry: ExtensionDescriptionRegistry; private readonly _resolvedExtensionsSet: Set<string>; private readonly _externalExtensionsMap: Map<string, ExtensionIdentifier>; private readonly _host: IExtensionsActivatorHost; <del> private readonly _activatingExtensions: Map<string, Promise<void>>; private readonly _activatedExtensions: Map<string, ActivatedExtension>; </del> <ins> private readonly _operations: Map<string, ActivationOperation>; </ins> /** * A map of already activated events to speed things up if the same activation event is triggered multiple times. */

host: IExtensionsActivatorHost, @ILogService private readonly _logService: ILogService ) { <del> this._isDisposed = false; </del> this._registry = registry; this._resolvedExtensionsSet = new Set<string>(); resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId))); this._externalExtensionsMap = new Map<string, ExtensionIdentifier>(); externalExtensions.forEach((extensionId) => this._externalExtensionsMap.set(ExtensionIdentifier.toKey(extensionId), extensionId)); this._host = host; <del> this._activatingExtensions = new Map<string, Promise<void>>(); this._activatedExtensions = new Map<string, ActivatedExtension>(); </del> <ins> this._operations = new Map<string, ActivationOperation>(); </ins> this._alreadyActivatedEvents = Object.create(null); } public dispose(): void { <del> this._isDisposed = true; </del> <ins> for (const [_, op] of this._operations) { op.dispose(); } </ins> } public isActivated(extensionId: ExtensionIdentifier): boolean { <del> const extensionKey = ExtensionIdentifier.toKey(extensionId); return this._activatedExtensions.has(extensionKey); </del> <ins> const op = this._operations.get(ExtensionIdentifier.toKey(extensionId)); return Boolean(op && op.value); </ins> } public getActivatedExtension(extensionId: ExtensionIdentifier): ActivatedExtension { <del> const extensionKey = ExtensionIdentifier.toKey(extensionId); const activatedExtension = this._activatedExtensions.get(extensionKey); if (!activatedExtension) { throw new Error('Extension `' + extensionId.value + '` is not known or not activated'); </del> <ins> const op = this._operations.get(ExtensionIdentifier.toKey(extensionId)); if (!op || !op.value) { throw new Error(`Extension '${extensionId.value}' is not known or not activated`); </ins> } <del> return activatedExtension; </del> <ins> return op.value; </ins> } <del> public activateByEvent(activationEvent: string, startup: boolean): Promise<void> { </del> <ins> public async activateByEvent(activationEvent: string, startup: boolean): Promise<void> { </ins> if (this._alreadyActivatedEvents[activationEvent]) { <del> return NO_OP_VOID_PROMISE; </del> <ins> return; </ins> } const activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); <del> return this._activateExtensions(activateExtensions.map(e => ({ </del> <ins> await this._activateExtensions(activateExtensions.map(e => ({ </ins> id: e.identifier, reason: { startup, extensionId: e.identifier, activationEvent } <del> }))).then(() => { this._alreadyActivatedEvents[activationEvent] = true; }); </del> <ins> }))); this._alreadyActivatedEvents[activationEvent] = true; </ins> } public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> { const desc = this._registry.getExtensionDescription(extensionId); if (!desc) { <del> throw new Error('Extension `' + extensionId + '` is not known'); </del> <ins> throw new Error(`Extension '${extensionId}' is not known`); </ins> } <ins> return this._activateExtensions([{ id: desc.identifier, reason }]); } </ins> <del> return this._activateExtensions([{ id: desc.identifier, reason }]); </del> <ins> private async _activateExtensions(extensions: ActivationIdAndReason[]): Promise<void> { const operations = extensions .filter((p) => !this.isActivated(p.id)) .map(ext => this._handleActivationRequest(ext)); await Promise.all(operations.map(op => op.wait())); </ins> } /** * Handle semantics related to dependencies for `currentExtension`. <del> * semantics: `redExtensions` must wait for `greenExtensions`. </del> <ins> * We don't need to worry about dependency loops because they are handled by the registry. </ins> */ <del> private _handleActivateRequest(currentActivation: ActivationIdAndReason, greenExtensions: { [id: string]: ActivationIdAndReason }, redExtensions: ActivationIdAndReason[]): void { </del> <ins> private _handleActivationRequest(currentActivation: ActivationIdAndReason): ActivationOperation { if (this._operations.has(ExtensionIdentifier.toKey(currentActivation.id))) { return this._operations.get(ExtensionIdentifier.toKey(currentActivation.id))!; } </ins> if (this._externalExtensionsMap.has(ExtensionIdentifier.toKey(currentActivation.id))) { <del> greenExtensions[ExtensionIdentifier.toKey(currentActivation.id)] = currentActivation; return; </del> <ins> return this._createAndSaveOperation(currentActivation, null, [], null); </ins> } const currentExtension = this._registry.getExtensionDescription(currentActivation.id); if (!currentExtension) { // Error condition 0: unknown extension const error = new Error(`Cannot activate unknown extension '${currentActivation.id.value}'`); <ins> const result = this._createAndSaveOperation(currentActivation, null, [], new FailedExtension(error)); </ins> this._host.onExtensionActivationError( currentActivation.id, error, new MissingExtensionDependency(currentActivation.id.value) ); <del> this._activatedExtensions.set(ExtensionIdentifier.toKey(currentActivation.id), new FailedExtension(error)); return; </del> <ins> return result; </ins> } <ins> const deps: ActivationOperation[] = []; </ins> const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); <del> let currentExtensionGetsGreenLight = true; for (let j = 0, lenJ = depIds.length; j < lenJ; j++) { const depId = depIds[j]; </del> <ins> for (const depId of depIds) { </ins> if (this._resolvedExtensionsSet.has(ExtensionIdentifier.toKey(depId))) { // This dependency is already resolved continue; } <del> const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId)); if (dep && !dep.activationFailed) { // the dependency is already activated OK </del> <ins> const dep = this._operations.get(ExtensionIdentifier.toKey(depId)); if (dep) { deps.push(dep); </ins> continue; } <del> if (dep && dep.activationFailed) { // Error condition 2: a dependency has already failed activation const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value; const depDesc = this._registry.getExtensionDescription(depId); const depFriendlyName = (depDesc ? depDesc.displayName || depId : depId); const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because its dependency '${depFriendlyName}' failed to activate`); (<any>error).detail = dep.activationFailedError; this._host.onExtensionActivationError( currentExtension.identifier, error, null ); this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); return; } </del> if (this._externalExtensionsMap.has(ExtensionIdentifier.toKey(depId))) { // must first wait for the dependency to activate <del> currentExtensionGetsGreenLight = false; greenExtensions[ExtensionIdentifier.toKey(depId)] = { </del> <ins> deps.push(this._handleActivationRequest({ </ins> id: this._externalExtensionsMap.get(ExtensionIdentifier.toKey(depId))!, reason: currentActivation.reason <del> }; </del> <ins> })); </ins> continue; }

} // must first wait for the dependency to activate <del> currentExtensionGetsGreenLight = false; greenExtensions[ExtensionIdentifier.toKey(depId)] = { </del> <ins> deps.push(this._handleActivationRequest({ </ins> id: depDesc.identifier, reason: currentActivation.reason <del> }; </del> <ins> })); </ins> continue; } // Error condition 1: unknown dependency const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value; const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because it depends on unknown extension '${depId}'`); <ins> const result = this._createAndSaveOperation(currentActivation, currentExtension.displayName, [], new FailedExtension(error)); </ins> this._host.onExtensionActivationError( currentExtension.identifier, error, new MissingExtensionDependency(depId) ); <del> this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); return; </del> <ins> return result; </ins> } <del> if (currentExtensionGetsGreenLight) { greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentActivation; } else { redExtensions.push(currentActivation); } </del> <ins> return this._createAndSaveOperation(currentActivation, currentExtension.displayName, deps, null); </ins> } <del> private _activateExtensions(extensions: ActivationIdAndReason[]): Promise<void> { if (extensions.length === 0) { return Promise.resolve(undefined); } </del> <ins> private _createAndSaveOperation(activation: ActivationIdAndReason, displayName: string | null | undefined, deps: ActivationOperation[], value: ActivatedExtension | null): ActivationOperation { const operation = new ActivationOperation(activation.id, displayName, activation.reason, deps, value, this._host, this._logService); this._operations.set(ExtensionIdentifier.toKey(activation.id), operation); return operation; } } </ins> <del> extensions = extensions.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p.id))); if (extensions.length === 0) { return Promise.resolve(undefined); } </del> <ins> class ActivationOperation { </ins> <del> const greenMap: { [id: string]: ActivationIdAndReason } = Object.create(null), red: ActivationIdAndReason[] = []; </del> <ins> private readonly _barrier = new Barrier(); private _isDisposed = false; </ins> <del> for (let i = 0, len = extensions.length; i < len; i++) { this._handleActivateRequest(extensions[i], greenMap, red); } </del> <ins> public get value(): ActivatedExtension | null { return this._value; } </ins> <del> // Make sure no red is also green for (let i = 0, len = red.length; i < len; i++) { const redExtensionKey = ExtensionIdentifier.toKey(red[i].id); if (greenMap[redExtensionKey]) { delete greenMap[redExtensionKey]; } } </del> <ins> public get friendlyName(): string { return this._displayName || this._id.value; } </ins> <del> const green = Object.keys(greenMap).map(id => greenMap[id]); </del> <ins> constructor( private readonly _id: ExtensionIdentifier, private readonly _displayName: string | null | undefined, private readonly _reason: ExtensionActivationReason, private readonly _deps: ActivationOperation[], private _value: ActivatedExtension | null, private readonly _host: IExtensionsActivatorHost, @ILogService private readonly _logService: ILogService ) { this._initialize(); } </ins> <del> if (red.length === 0) { // Finally reached only leafs! return Promise.all(green.map((p) => this._activateExtension(p.id, p.reason))).then(_ => undefined); } </del> <ins> public dispose(): void { this._isDisposed = true; } </ins> <del> return this._activateExtensions(green).then(_ => { return this._activateExtensions(red); }); </del> <ins> public wait() { return this._barrier.wait(); </ins> } <del> private _activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> { const extensionKey = ExtensionIdentifier.toKey(extensionId); </del> <ins> private async _initialize(): Promise<void> { await this._waitForDepsThenActivate(); this._barrier.open(); } </ins> <del> if (this._activatedExtensions.has(extensionKey)) { return Promise.resolve(undefined); </del> <ins> private async _waitForDepsThenActivate(): Promise<void> { if (this._value) { // this operation is already finished return; </ins> } <del> const currentlyActivatingExtension = this._activatingExtensions.get(extensionKey); if (currentlyActivatingExtension) { return currentlyActivatingExtension; </del> <ins> while (this._deps.length > 0) { // remove completed deps for (let i = 0; i < this._deps.length; i++) { const dep = this._deps[i]; if (dep.value && !dep.value.activationFailed) { // the dependency is already activated OK this._deps.splice(i, 1); i--; continue; } if (dep.value && dep.value.activationFailed) { // Error condition 2: a dependency has already failed activation const error = new Error(`Cannot activate the '${this.friendlyName}' extension because its dependency '${dep.friendlyName}' failed to activate`); (<any>error).detail = dep.value.activationFailedError; this._value = new FailedExtension(error); this._host.onExtensionActivationError(this._id, error, null); return; } } if (this._deps.length > 0) { // wait for one dependency await Promise.race(this._deps.map(dep => dep.wait())); } </ins> } <del> const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => { </del> <ins> await this._activate(); } private async _activate(): Promise<void> { try { this._value = await this._host.actualActivateExtension(this._id, this._reason); } catch (err) { </ins> const error = new Error(); if (err && err.name) { error.name = err.name; } if (err && err.message) { <del> error.message = `Activating extension '${extensionId.value}' failed: ${err.message}.`; </del> <ins> error.message = `Activating extension '${this._id.value}' failed: ${err.message}.`; </ins> } else { <del> error.message = `Activating extension '${extensionId.value}' failed: ${err}.`; </del> <ins> error.message = `Activating extension '${this._id.value}' failed: ${err}.`; </ins> } if (err && err.stack) { error.stack = err.stack; } <ins> // Treat the extension as being empty this._value = new FailedExtension(error); </ins> if (this._isDisposed && errors.isCancellationError(err)) { // It is expected for ongoing activations to fail if the extension host is going down // So simply ignore and don't log canceled errors in this case <del> return new FailedExtension(err); </del> <ins> return; </ins> } <del> this._host.onExtensionActivationError( extensionId, error, null ); this._logService.error(`Activating extension ${extensionId.value} failed due to an error:`); </del> <ins> this._host.onExtensionActivationError(this._id, error, null); this._logService.error(`Activating extension ${this._id.value} failed due to an error:`); </ins> this._logService.error(err); <del> // Treat the extension as being empty return new FailedExtension(err); }).then((x: ActivatedExtension) => { this._activatedExtensions.set(extensionKey, x); this._activatingExtensions.delete(extensionKey); }); this._activatingExtensions.set(extensionKey, newlyActivatingExtension); return newlyActivatingExtension; </del> <ins> } </ins> } }

assert.deepStrictEqual(host.errors[1][0], idA); }); <ins> test('issue #144518: Problem with git extension and vscode-icons', async () => { const extActivationA = new ExtensionActivationPromiseSource(); const extActivationB = new ExtensionActivationPromiseSource(); const extActivationC = new ExtensionActivationPromiseSource(); const host = new PromiseExtensionsActivatorHost([ [idA, extActivationA], [idB, extActivationB], [idC, extActivationC] ]); const activator = createActivator(host, [ desc(idA, [idB]), desc(idB), desc(idC), ]); activator.activateByEvent('*', false); assert.deepStrictEqual(host.activateCalls, [idB, idC]); extActivationB.resolve(); await timeout(0); assert.deepStrictEqual(host.activateCalls, [idB, idC, idA]); extActivationA.resolve(); }); </ins> class SimpleExtensionsActivatorHost implements IExtensionsActivatorHost { public readonly activateCalls: ExtensionIdentifier[] = []; public readonly errors: [ExtensionIdentifier, Error | null, MissingExtensionDependency | null][] = [];