fix(core) improve topological sorting performance

This commit is contained in:
Kamil Myśliwiec
2019-09-03 09:53:56 +02:00
parent 12368ffca7
commit 77fb3bae6b
6 changed files with 37 additions and 64 deletions

View File

@@ -47,7 +47,7 @@ describe('Middleware (execution order)', () => {
await app.init();
});
it(`should execute middleware of modules that are closer to the root module`, () => {
it(`should execute middleware in topological order`, () => {
return request(app.getHttpServer())
.get('/hello')
.expect(200, RETURN_VALUE_B);

View File

@@ -120,13 +120,8 @@ export class Module {
return this._distance;
}
public updateDistance(distance: number, stack: Module[]) {
this._distance = distance;
Array.from(this._imports)
.filter(module => module && !stack.includes(this))
.forEach(module =>
module.updateDistance(distance + 1, stack.concat(this)),
);
set distance(value: number) {
this._distance = value;
}
public addCoreProviders(container: NestContainer) {
@@ -417,9 +412,6 @@ export class Module {
public addRelatedModule(module: Module) {
this._imports.add(module);
if (this._distance + 1 > module._distance) {
module.updateDistance(this._distance + 1, [this]);
}
}
public replace(toReplace: string | symbol | Type<any>, options: any) {

View File

@@ -68,14 +68,15 @@ export class MiddlewareModule {
modules: Map<string, Module>,
) {
const moduleEntries = [...modules.entries()];
await Promise.all(
moduleEntries.map(async ([name, module]) => {
const instance = module.instance;
await this.loadConfiguration(middlewareContainer, instance, name);
await this.resolver.resolveInstances(module, name);
}),
);
const loadMiddlewareConfiguration = async ([name, module]: [
string,
Module,
]) => {
const instance = module.instance;
await this.loadConfiguration(middlewareContainer, instance, name);
await this.resolver.resolveInstances(module, name);
};
await Promise.all(moduleEntries.map(loadMiddlewareConfiguration));
}
public async loadConfiguration(

View File

@@ -12,11 +12,9 @@ export class MiddlewareResolver {
const middleware = this.middlewareContainer.getMiddlewareCollection(
moduleName,
);
await Promise.all(
[...middleware.values()].map(async wrapper =>
this.resolveMiddlewareInstance(wrapper, middleware, module),
),
);
const resolveInstance = async (wrapper: InstanceWrapper) =>
this.resolveMiddlewareInstance(wrapper, middleware, module);
await Promise.all([...middleware.values()].map(resolveInstance));
}
private async resolveMiddlewareInstance(

View File

@@ -36,6 +36,7 @@ import {
import { ApplicationConfig } from './application-config';
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants';
import { CircularDependencyException } from './errors/exceptions/circular-dependency.exception';
import { ModulesContainer } from './injector';
import { NestContainer } from './injector/container';
import { InstanceWrapper } from './injector/instance-wrapper';
import { Module } from './injector/module';
@@ -116,6 +117,7 @@ export class DependenciesScanner {
this.reflectControllers(metatype, token);
this.reflectExports(metatype, token);
}
this.calculateModulesDistance(modules);
}
public async reflectImports(
@@ -251,6 +253,26 @@ export class DependenciesScanner {
return undefined;
}
public async calculateModulesDistance(modules: ModulesContainer) {
const modulesStack = [];
const modulesGenerator = modules.values();
const rootModule = modulesGenerator.next().value;
const calculateDistance = (moduleRef: Module, distance = 1) => {
if (modulesStack.includes(moduleRef)) {
return;
}
modulesStack.push(moduleRef);
const moduleImports = rootModule.relatedModules;
moduleImports.forEach(module => {
module.distance = distance;
calculateDistance(module, distance + 1);
});
};
calculateDistance(rootModule);
}
public async insertImport(related: any, token: string, context: string) {
if (isUndefined(related)) {
throw new CircularDependencyException(context);

View File

@@ -481,44 +481,4 @@ describe('Module', () => {
});
});
});
describe('getter "distance"', () => {
@ModuleDecorator({})
class ModuleA {}
@ModuleDecorator({})
class ModuleB {}
@ModuleDecorator({})
class ModuleC {}
let moduleA, moduleB, moduleC;
beforeEach(() => {
moduleA = new Module(ModuleA as any, [], container);
moduleB = new Module(ModuleB as any, [], container);
moduleC = new Module(ModuleC as any, [], container);
});
it('should calculate "distance" properly', () => {
moduleA.addRelatedModule(moduleB);
moduleC.addRelatedModule(moduleB);
moduleA.addRelatedModule(moduleC);
expect(moduleA.distance).to.be.equal(0);
expect(moduleB.distance).to.be.equal(2);
expect(moduleC.distance).to.be.equal(1);
});
it('should not throw an exception when circular dependency occurs', () => {
expect(() => {
moduleA.addRelatedModule(moduleB);
moduleB.addRelatedModule(moduleA);
}).to.not.throw;
expect(() => {
moduleA.addRelatedModule(moduleB);
moduleB.addRelatedModule(moduleC);
moduleC.addRelatedModule(moduleA);
}).to.not.throw;
});
});
});