提交 9b669a0c 编写于 作者: A Alex Dima

Remove extensions with dependency loops as soon as possible

上级 b909133b
...@@ -231,7 +231,7 @@ export class ExtensionsActivator { ...@@ -231,7 +231,7 @@ export class ExtensionsActivator {
return NO_OP_VOID_PROMISE; return NO_OP_VOID_PROMISE;
} }
let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
return this._activateExtensions(activateExtensions, reason, 0).then(() => { return this._activateExtensions(activateExtensions, reason).then(() => {
this._alreadyActivatedEvents[activationEvent] = true; this._alreadyActivatedEvents[activationEvent] = true;
}); });
} }
...@@ -242,7 +242,7 @@ export class ExtensionsActivator { ...@@ -242,7 +242,7 @@ export class ExtensionsActivator {
throw new Error('Extension `' + extensionId + '` is not known'); throw new Error('Extension `' + extensionId + '` is not known');
} }
return this._activateExtensions([desc], reason, 0); return this._activateExtensions([desc], reason);
} }
/** /**
...@@ -295,7 +295,7 @@ export class ExtensionsActivator { ...@@ -295,7 +295,7 @@ export class ExtensionsActivator {
} }
} }
private _activateExtensions(extensionDescriptions: IExtensionDescription[], reason: ExtensionActivationReason, recursionLevel: number): Promise<void> { private _activateExtensions(extensionDescriptions: IExtensionDescription[], reason: ExtensionActivationReason): Promise<void> {
// console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id)); // console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id));
if (extensionDescriptions.length === 0) { if (extensionDescriptions.length === 0) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
...@@ -306,17 +306,6 @@ export class ExtensionsActivator { ...@@ -306,17 +306,6 @@ export class ExtensionsActivator {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
if (recursionLevel > 10) {
// More than 10 dependencies deep => most likely a dependency loop
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
// Error condition 3: dependency loop
this._host.showMessage(Severity.Error, nls.localize('failedDep2', "Extension '{0}' failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].identifier.value));
const error = new Error('More than 10 levels of dependencies (most likely a dependency loop)');
this._activatedExtensions.set(ExtensionIdentifier.toKey(extensionDescriptions[i].identifier), new FailedExtension(error));
}
return Promise.resolve(undefined);
}
let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null), let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null),
red: IExtensionDescription[] = []; red: IExtensionDescription[] = [];
...@@ -342,8 +331,8 @@ export class ExtensionsActivator { ...@@ -342,8 +331,8 @@ export class ExtensionsActivator {
return Promise.all(green.map((p) => this._activateExtension(p, reason))).then(_ => undefined); return Promise.all(green.map((p) => this._activateExtension(p, reason))).then(_ => undefined);
} }
return this._activateExtensions(green, reason, recursionLevel + 1).then(_ => { return this._activateExtensions(green, reason).then(_ => {
return this._activateExtensions(red, reason, recursionLevel + 1); return this._activateExtensions(red, reason);
}); });
} }
......
...@@ -227,7 +227,11 @@ export class ExtensionService extends Disposable implements IExtensionService { ...@@ -227,7 +227,11 @@ export class ExtensionService extends Disposable implements IExtensionService {
} }
// Update the local registry // Update the local registry
this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier)); const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
toRemove = toRemove.concat(result.removedDueToLooping);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
// Update extension points // Update extension points
this._rehandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove)); this._rehandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
...@@ -625,12 +629,16 @@ export class ExtensionService extends Disposable implements IExtensionService { ...@@ -625,12 +629,16 @@ export class ExtensionService extends Disposable implements IExtensionService {
const enabledExtensions = await this._getRuntimeExtensions(extensions); const enabledExtensions = await this._getRuntimeExtensions(extensions);
this._handleExtensionPoints(enabledExtensions); this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier)); extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
this._releaseBarrier(); this._releaseBarrier();
} }
private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void { private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void {
this._registry = new ExtensionDescriptionRegistry(allExtensions); this._registry = new ExtensionDescriptionRegistry([]);
const result = this._registry.deltaExtensions(allExtensions, []);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
let availableExtensions = this._registry.getAllExtensionDescriptions(); let availableExtensions = this._registry.getAllExtensionDescriptions();
let extensionPoints = ExtensionsRegistry.getExtensionPoints(); let extensionPoints = ExtensionsRegistry.getExtensionPoints();
......
...@@ -7,6 +7,12 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e ...@@ -7,6 +7,12 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
export class DeltaExtensionsResult {
constructor(
public readonly removedDueToLooping: IExtensionDescription[]
) { }
}
export class ExtensionDescriptionRegistry { export class ExtensionDescriptionRegistry {
private readonly _onDidChange = new Emitter<void>(); private readonly _onDidChange = new Emitter<void>();
public readonly onDidChange = this._onDidChange.event; public readonly onDidChange = this._onDidChange.event;
...@@ -60,19 +66,120 @@ export class ExtensionDescriptionRegistry { ...@@ -60,19 +66,120 @@ export class ExtensionDescriptionRegistry {
this._onDidChange.fire(undefined); this._onDidChange.fire(undefined);
} }
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]) { public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult {
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); if (toAdd.length > 0) {
const toRemoveSet = new Set<string>(); this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); }
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
// Immediately remove looping extensions!
const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions);
toRemove = toRemove.concat(looping.map(ext => ext.identifier));
if (toRemove.length > 0) {
const toRemoveSet = new Set<string>();
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
}
this._initialize(); this._initialize();
this._onDidChange.fire(undefined); this._onDidChange.fire(undefined);
return new DeltaExtensionsResult(looping);
}
private static _findLoopingExtensions(extensionDescriptions: IExtensionDescription[]): IExtensionDescription[] {
const G = new class {
private _arcs = new Map<string, string[]>();
private _nodesSet = new Set<string>();
private _nodesArr: string[] = [];
addNode(id: string): void {
if (!this._nodesSet.has(id)) {
this._nodesSet.add(id);
this._nodesArr.push(id);
}
}
addArc(from: string, to: string): void {
this.addNode(from);
this.addNode(to);
if (this._arcs.has(from)) {
this._arcs.get(from).push(to);
} else {
this._arcs.set(from, [to]);
}
}
getArcs(id: string): string[] {
if (this._arcs.has(id)) {
return this._arcs.get(id);
}
return [];
}
hasOnlyGoodArcs(id: string, good: Set<string>): boolean {
const dependencies = G.getArcs(id);
for (let i = 0; i < dependencies.length; i++) {
if (!good.has(dependencies[i])) {
return false;
}
}
return true;
}
getNodes(): string[] {
return this._nodesArr;
}
};
let descs = new Map<string, IExtensionDescription>();
for (let extensionDescription of extensionDescriptions) {
const extensionId = ExtensionIdentifier.toKey(extensionDescription.identifier);
descs.set(extensionId, extensionDescription);
if (extensionDescription.extensionDependencies) {
for (let _depId of extensionDescription.extensionDependencies) {
const depId = ExtensionIdentifier.toKey(_depId);
G.addArc(extensionId, depId);
}
}
}
// initialize with all extensions with no dependencies.
let good = new Set<string>();
G.getNodes().filter(id => G.getArcs(id).length === 0).forEach(id => good.add(id));
// all other extensions will be processed below.
let nodes = G.getNodes().filter(id => !good.has(id));
let madeProgress: boolean;
do {
madeProgress = false;
// find one extension which has only good deps
for (let i = 0; i < nodes.length; i++) {
const id = nodes[i];
if (G.hasOnlyGoodArcs(id, good)) {
nodes.splice(i, 1);
i--;
good.add(id);
madeProgress = true;
}
}
} while (madeProgress);
// The remaining nodes are bad and have loops
return nodes.map(id => descs.get(id));
} }
public containsActivationEvent(activationEvent: string): boolean { public containsActivationEvent(activationEvent: string): boolean {
return this._activationMap.has(activationEvent); return this._activationMap.has(activationEvent);
} }
public containsExtension(extensionId: ExtensionIdentifier): boolean {
return this._extensionsMap.has(ExtensionIdentifier.toKey(extensionId));
}
public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] { public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] {
const extensions = this._activationMap.get(activationEvent); const extensions = this._activationMap.get(activationEvent);
return extensions ? extensions.slice(0) : []; return extensions ? extensions.slice(0) : [];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册