Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventHandler pattern fails to subscribe to Redis channel #7406

Closed
RoxKilly opened this issue Jul 6, 2021 · 9 comments
Closed

EventHandler pattern fails to subscribe to Redis channel #7406

RoxKilly opened this issue Jul 6, 2021 · 9 comments

Comments

@RoxKilly
Copy link

RoxKilly commented Jul 6, 2021

Bug Report

Current behavior

  • I successfully connect to Redis using client.connect()
  • I successfully emit an event using client.emit()
  • My controller handler decorated with @EventPattern does not get triggered

Input Code

Source code can be downloaded and run

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  await NestFactory.createMicroservice<RedisOptions>(AppModule, {
    transport: Transport.REDIS,
    options: { url: 'redis://localhost:6379' },
  });
  await app.listen(3000);
}
bootstrap();

AppModule

@Module({
  imports: [TransportModule],
  controllers: [AppController],
})
export class AppModule {}

AppController handler

  @EventPattern(EVENT_NAME) // <- EVENT_NAME is string constant 'EVENT_NAME'
  onEvent(data: any) {
    Logger.log('Event received'); // <- this is never executed
    Logger.log(data);
  }

TransportModule

export const EVENT_NAME = 'EVENT_NAME';
export const NEST_REDIS = Symbol('NEST_REDIS');

export const RedisProvider: Provider = {
  provide: NEST_REDIS,
  useFactory: () =>
    ClientProxyFactory.create({
      transport: Transport.REDIS,
      options: { url: 'redis://localhost:6379' },
    }),
};

@Module({ providers: [RedisProvider] })
export class TransportModule implements OnApplicationBootstrap {
  constructor(@Inject(NEST_REDIS) private client: ClientRedis) {}

  onApplicationBootstrap() {
    this.client.connect().then(() => {
      Logger.log('REDIS is connected');
      this.startEmitting();
    });
  }

  startEmitting() {
    let msgNumber = 0;

    // wait 3 secs, then start emitting every 8
    timer(3000, 8000)
      .pipe(
        tap(() => Logger.log(`Emitting message #${++msgNumber}`)),
        tap(() => this.client.emit(EVENT_NAME, { messageNumber: msgNumber })),
      )
      .subscribe();
  }
}

Application Log

Screenshot 2021-07-05 204414

redis-cli log

Screenshot 2021-07-05 210505

In the redis-cli log above, the 3rd line of response to my subscribe call is (integer) 1; this leads me to believe that NestJS is not subscribed to this channel at all (that the redis-cli is the 1 subscriber)

redis-server log

When the application starts up, the redis-server log shows two accepted connections on consecutive ports (I assume that's when I make the client.connect() call)

Expected behavior

I expected that when I use client.emit(EVENT_NAME), the controller handler decorated with @EventPattern(EVENT_NAME) would be executed with the event payload

Possible Solution

Environment


Nest version: 7.6.18
Redis 6.2.4
redis (NPM library) 3.1.2

 
For Tooling issues:
- Node version: 16.4.0 
- Platform:  Windows 10, but Redis is installed and running from the Windows subsystem for Linux
 
Other:
No error logged anywhere that I can find

Sorry for the long post. In case you missed it, the source files are available here.

@RoxKilly RoxKilly added the needs triage This issue has not been looked into label Jul 6, 2021
@RoxKilly RoxKilly changed the title EventHandler pattern fails to subscribe to Redis event EventHandler pattern fails to subscribe to Redis channel Jul 6, 2021
@RoxKilly
Copy link
Author

RoxKilly commented Jul 6, 2021

also posted on StackOverflow in case this is user error and not a bug

@RoxKilly
Copy link
Author

RoxKilly commented Jul 6, 2021

Interestingly, if I try to take over the protected subClient within the ClientRedis instance of NestJS, I can subscribe to and receive those messages. Consider this code, which I've just inserted at the top of TransportModule.startEmitting from earlier:

  listen() {
    this.client['subClient'].subscribe(EVENT_NAME);
    this.client['subClient'].on('message', (channel, message) => {
      console.log(`subClient received; channel=${channel}, message=${message}`);
    });
  }

With this running, the controller handler still does not get triggered, but I can now see the messages come through in the logs:

Screenshot 2021-07-05 234959

@kamilmysliwiec
Copy link
Member

Please, provide a minimum reproduction repository on Github.

@kamilmysliwiec kamilmysliwiec added needs clarification and removed needs triage This issue has not been looked into labels Jul 6, 2021
@RoxKilly
Copy link
Author

RoxKilly commented Jul 6, 2021

@kamilmysliwiec
Copy link
Member

Your minimum reproduction repository is invalid. In the main.ts file, you create the microservice instance:

await NestFactory.createMicroservice<RedisOptions>(AppModule, {
  transport: Transport.REDIS,
  options: { url: 'redis://localhost:6379' },
});

but since it's not assigned to any variable, it's automatically garbage collected (and the Redis "consumer" does not even bootstrap).

After making the following changes:

image

everything will start working as expected:

image

@RoxKilly
Copy link
Author

RoxKilly commented Jul 6, 2021

Thank you @kamilmysliwiec I suspected as much. May I suggest that at least one of the examples on the Microservices section of the docs shows or links to this logAsync usage ? I read through that page, and also the Redis page several times over in the last 12hrs but the answer isn't there.

I appreciate your time, and I'm a big fan of your project.

@RoxKilly
Copy link
Author

RoxKilly commented Jul 7, 2021

Hello. The solution offered above creates two full instances of the application (I have 2 AppModules and everything else). This led to some hard to debug errors.

In Kamil's solution, I changed this line::

const microservice =  await NestFactory.createMicroservice<RedisOptions>(AppModule, {
    transport: Transport.REDIS,
    options: { url: 'redis://localhost:6379' },
  });

To this:

const microservice = app.connectMicroservice({
    transport: Transport.REDIS,
    options: { url: 'redis://localhost:6379' },
  });

@RoxKilly
Copy link
Author

RoxKilly commented Jul 7, 2021

The relevant section of the documentation:
https://docs.nestjs.com/faq/hybrid-application#hybrid-application

@kamilmysliwiec
Copy link
Member

Yep, that was just an example. Using hybrid apps approach is totally fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants