Skip to content

Delete from RedisCoreModule.connectionTokens during shutdown instead of clearing#307

Open
drew-r wants to merge 1 commit into
nest-modules:mainfrom
drew-r:main
Open

Delete from RedisCoreModule.connectionTokens during shutdown instead of clearing#307
drew-r wants to merge 1 commit into
nest-modules:mainfrom
drew-r:main

Conversation

@drew-r

@drew-r drew-r commented Jun 16, 2026

Copy link
Copy Markdown

Fixes bug causing connections not to be quit when >1 named provider.

Why it hangs with 2+ Connections:

  1. Shared Static Set: RedisCoreModule.connectionTokens is a static Set shared across all module registrations. When you register both e.g REDIS_MAIN and REDIS_SUBSCRIBER, both tokens are added to this single static Set.

  2. Multiple Shutdown Invocations: Because RedisCoreModule is imported multiple times (once for each connection), NestJS instantiates multiple instances of RedisCoreModule and calls onApplicationShutdown on each of them during teardown.

  3. The Cleared Set Bug:

    • First Invocation (onApplicationShutdown of Instance 1):

      • It loops over all tokens in the static Set (REDIS_MAIN_... and REDIS_SUBSCRIBER_...).
      • For REDIS_MAIN_..., this.moduleRef.get() succeeds, and it successfully quits the connection gracefully.
      • For REDIS_SUBSCRIBER_..., this.moduleRef.get() fails and throws an exception because that provider belongs to a different dynamic module's local scope/context. This error is silently caught and swallowed by the empty catch {} block.
      • At the end of this loop, it calls RedisCoreModule_1.connectionTokens.clear(). This wipes out the static Set entirely.
    • Second Invocation (onApplicationShutdown of Instance 2):

      • Since the static connectionTokens Set was cleared in the previous step, it is now empty.
      • The loop does not run at all.
      • As a result, the second connection (REDIS_SUBSCRIBER) is never quit or disconnected.

Replication steps:

nest new foo;
cd foo;
npm i;
npm i @nestjs-modules/ioredis ioredis;

Set app.module.ts like so

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RedisModule } from '@nestjs-modules/ioredis';

@Module({
  imports: [    
    RedisModule.forRootAsync({
      useFactory: () => ({        
        type: 'single',
        url: 'foo',
      })
    }, 'redis1'),
    RedisModule.forRootAsync({
      useFactory: () => ({        
        type: 'single',
        url: 'foo',
      })
    }, 'redis2'),
    
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Then run npm run test:e2e.

Expected:
Executes test, passes + exits.

Actual:
Executes test, passes + outputs

Jest did not exit one second after the test run has completed.

'This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

& doesn't exit.

bug causing connections not to be quit when >1 named provider
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant