mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
Merge pull request #15504 from hajekjiri/bug/fix-undefined-injection
fix(core): fix race condition in class dependency resolution from imported modules
This commit is contained in:
@@ -49,21 +49,42 @@ class TransientProvider {}
|
||||
@Injectable()
|
||||
class RequestProvider {}
|
||||
|
||||
@Injectable()
|
||||
class ForeignTransientProvider {}
|
||||
|
||||
@Injectable()
|
||||
export class Dependant {
|
||||
constructor(
|
||||
private readonly transientProvider: TransientProvider,
|
||||
|
||||
private readonly foreignTransientProvider: ForeignTransientProvider,
|
||||
|
||||
@Inject(RequestProvider)
|
||||
private readonly requestProvider: RequestProvider,
|
||||
) {}
|
||||
|
||||
public checkDependencies() {
|
||||
expect(this.transientProvider).to.be.instanceOf(TransientProvider);
|
||||
expect(this.foreignTransientProvider).to.be.instanceOf(
|
||||
ForeignTransientProvider,
|
||||
);
|
||||
expect(this.requestProvider).to.be.instanceOf(RequestProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: ForeignTransientProvider,
|
||||
scope: Scope.TRANSIENT,
|
||||
useClass: ForeignTransientProvider,
|
||||
},
|
||||
],
|
||||
exports: [ForeignTransientProvider],
|
||||
})
|
||||
export class ModuleWithForeignTransientProvider {}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
@@ -98,6 +119,12 @@ export class GlobalModuleWithRequestProvider {}
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
/*
|
||||
* ForeginTransientProvider will be resolved quickly because its host module is imported first.
|
||||
* IMPORTANT: Do not move this module, otherwise we may not catch future regressions.
|
||||
*/
|
||||
ModuleWithForeignTransientProvider,
|
||||
|
||||
GlobalModule1,
|
||||
GlobalModule2,
|
||||
GlobalModule3,
|
||||
@@ -115,7 +142,7 @@ export class GlobalModuleWithRequestProvider {}
|
||||
export class AppModule {}
|
||||
|
||||
describe('Many global modules', () => {
|
||||
it('should inject request-scoped useFactory provider and transient-scoped useClass provider from different modules', async () => {
|
||||
it('should inject request-scoped and transient-scoped providers from different modules', async () => {
|
||||
const moduleBuilder = Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
});
|
||||
|
||||
@@ -2263,6 +2263,22 @@
|
||||
},
|
||||
"id": "-1303681274"
|
||||
},
|
||||
"-831049991": {
|
||||
"source": "594986539",
|
||||
"target": "-1721730431",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "InputModule",
|
||||
"sourceClassName": "InputService",
|
||||
"targetClassName": "CircularService",
|
||||
"sourceClassToken": "InputService",
|
||||
"targetClassToken": "CircularService",
|
||||
"targetModuleName": "CircularModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-831049991"
|
||||
},
|
||||
"-886102564": {
|
||||
"source": "208171089",
|
||||
"target": "671882984",
|
||||
@@ -2280,6 +2296,22 @@
|
||||
},
|
||||
"id": "-886102564"
|
||||
},
|
||||
"-2146943494": {
|
||||
"source": "-234035039",
|
||||
"target": "928565345",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "RequestChainModule",
|
||||
"sourceClassName": "RequestChainService",
|
||||
"targetClassName": "HelperService",
|
||||
"sourceClassToken": "RequestChainService",
|
||||
"targetClassToken": "HelperService",
|
||||
"targetModuleName": "HelperModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-2146943494"
|
||||
},
|
||||
"-2003045613": {
|
||||
"source": "-377928898",
|
||||
"target": "-616397055",
|
||||
@@ -2312,38 +2344,6 @@
|
||||
},
|
||||
"id": "-881420795"
|
||||
},
|
||||
"-831049991": {
|
||||
"source": "594986539",
|
||||
"target": "-1721730431",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "InputModule",
|
||||
"sourceClassName": "InputService",
|
||||
"targetClassName": "CircularService",
|
||||
"sourceClassToken": "InputService",
|
||||
"targetClassToken": "CircularService",
|
||||
"targetModuleName": "CircularModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-831049991"
|
||||
},
|
||||
"-2146943494": {
|
||||
"source": "-234035039",
|
||||
"target": "928565345",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "RequestChainModule",
|
||||
"sourceClassName": "RequestChainService",
|
||||
"targetClassName": "HelperService",
|
||||
"sourceClassToken": "RequestChainService",
|
||||
"targetClassToken": "HelperService",
|
||||
"targetModuleName": "HelperModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-2146943494"
|
||||
},
|
||||
"-1816180282": {
|
||||
"source": "-848516688",
|
||||
"target": "-1673986099",
|
||||
|
||||
@@ -2181,22 +2181,6 @@
|
||||
},
|
||||
"id": "1976848738"
|
||||
},
|
||||
"-2105726668": {
|
||||
"source": "-1803759743",
|
||||
"target": "1010833816",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "PropertiesModule",
|
||||
"sourceClassName": "PropertiesService",
|
||||
"targetClassName": "token",
|
||||
"sourceClassToken": "PropertiesService",
|
||||
"targetClassToken": "token",
|
||||
"targetModuleName": "PropertiesModule",
|
||||
"keyOrIndex": "token",
|
||||
"injectionType": "property"
|
||||
},
|
||||
"id": "-2105726668"
|
||||
},
|
||||
"-21463590": {
|
||||
"source": "-1378706112",
|
||||
"target": "1004276345",
|
||||
@@ -2213,6 +2197,22 @@
|
||||
},
|
||||
"id": "-21463590"
|
||||
},
|
||||
"-2105726668": {
|
||||
"source": "-1803759743",
|
||||
"target": "1010833816",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "PropertiesModule",
|
||||
"sourceClassName": "PropertiesService",
|
||||
"targetClassName": "token",
|
||||
"sourceClassToken": "PropertiesService",
|
||||
"targetClassToken": "token",
|
||||
"targetModuleName": "PropertiesModule",
|
||||
"keyOrIndex": "token",
|
||||
"injectionType": "property"
|
||||
},
|
||||
"id": "-2105726668"
|
||||
},
|
||||
"-1657371464": {
|
||||
"source": "-1673986099",
|
||||
"target": "1919157847",
|
||||
@@ -2247,6 +2247,22 @@
|
||||
},
|
||||
"id": "-1303681274"
|
||||
},
|
||||
"-831049991": {
|
||||
"source": "594986539",
|
||||
"target": "-1721730431",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "InputModule",
|
||||
"sourceClassName": "InputService",
|
||||
"targetClassName": "CircularService",
|
||||
"sourceClassToken": "InputService",
|
||||
"targetClassToken": "CircularService",
|
||||
"targetModuleName": "CircularModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-831049991"
|
||||
},
|
||||
"-886102564": {
|
||||
"source": "208171089",
|
||||
"target": "671882984",
|
||||
@@ -2264,6 +2280,22 @@
|
||||
},
|
||||
"id": "-886102564"
|
||||
},
|
||||
"-2146943494": {
|
||||
"source": "-234035039",
|
||||
"target": "928565345",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "RequestChainModule",
|
||||
"sourceClassName": "RequestChainService",
|
||||
"targetClassName": "HelperService",
|
||||
"sourceClassToken": "RequestChainService",
|
||||
"targetClassToken": "HelperService",
|
||||
"targetModuleName": "HelperModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-2146943494"
|
||||
},
|
||||
"-2003045613": {
|
||||
"source": "-377928898",
|
||||
"target": "-616397055",
|
||||
@@ -2296,38 +2328,6 @@
|
||||
},
|
||||
"id": "-881420795"
|
||||
},
|
||||
"-831049991": {
|
||||
"source": "594986539",
|
||||
"target": "-1721730431",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "InputModule",
|
||||
"sourceClassName": "InputService",
|
||||
"targetClassName": "CircularService",
|
||||
"sourceClassToken": "InputService",
|
||||
"targetClassToken": "CircularService",
|
||||
"targetModuleName": "CircularModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-831049991"
|
||||
},
|
||||
"-2146943494": {
|
||||
"source": "-234035039",
|
||||
"target": "928565345",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "RequestChainModule",
|
||||
"sourceClassName": "RequestChainService",
|
||||
"targetClassName": "HelperService",
|
||||
"sourceClassToken": "RequestChainService",
|
||||
"targetClassToken": "HelperService",
|
||||
"targetModuleName": "HelperModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor"
|
||||
},
|
||||
"id": "-2146943494"
|
||||
},
|
||||
"-1816180282": {
|
||||
"source": "-848516688",
|
||||
"target": "-1673986099",
|
||||
|
||||
@@ -682,14 +682,11 @@ export class Injector {
|
||||
inquirerId,
|
||||
);
|
||||
if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
|
||||
wrapper.settlementSignal?.insertRef(instanceWrapperRef.id);
|
||||
|
||||
await this.loadProvider(
|
||||
instanceWrapperRef,
|
||||
relatedModule,
|
||||
contextId,
|
||||
wrapper,
|
||||
);
|
||||
/*
|
||||
* Provider will be loaded shortly in resolveComponentHost() once we pass the current
|
||||
* Barrier. We cannot load it here because doing so could incorrectly evaluate the
|
||||
* staticity of the dependency tree and lead to undefined / null injection.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,63 +447,6 @@ describe('Injector', () => {
|
||||
),
|
||||
).to.eventually.be.eq(null);
|
||||
});
|
||||
|
||||
it('should call "loadProvider" when component is not resolved', async () => {
|
||||
const moduleFixture = {
|
||||
imports: new Map([
|
||||
[
|
||||
'key',
|
||||
{
|
||||
providers: {
|
||||
has: () => true,
|
||||
get: () =>
|
||||
new InstanceWrapper({
|
||||
isResolved: false,
|
||||
}),
|
||||
},
|
||||
exports: {
|
||||
has: () => true,
|
||||
},
|
||||
imports: new Map(),
|
||||
},
|
||||
],
|
||||
] as any),
|
||||
};
|
||||
await injector.lookupComponentInImports(
|
||||
moduleFixture as any,
|
||||
metatype as any,
|
||||
new InstanceWrapper(),
|
||||
);
|
||||
expect(loadProvider.called).to.be.true;
|
||||
});
|
||||
|
||||
it('should not call "loadProvider" when component is resolved', async () => {
|
||||
const moduleFixture = {
|
||||
relatedModules: new Map([
|
||||
[
|
||||
'key',
|
||||
{
|
||||
providers: {
|
||||
has: () => true,
|
||||
get: () => ({
|
||||
isResolved: true,
|
||||
}),
|
||||
},
|
||||
exports: {
|
||||
has: () => true,
|
||||
},
|
||||
relatedModules: new Map(),
|
||||
},
|
||||
],
|
||||
] as any),
|
||||
};
|
||||
await injector.lookupComponentInImports(
|
||||
moduleFixture as any,
|
||||
metatype as any,
|
||||
null!,
|
||||
);
|
||||
expect(loadProvider.called).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveParamToken', () => {
|
||||
|
||||
Reference in New Issue
Block a user