RabbitMq - Channels and Connections not closing - node.js

I'm using the following Nodejs script to publish messages in my RabbitMq queue:
const amqp = require("amqplib");
[...]
const publishMsgToQueue = async (job, res) => {
const queue = job.queueName;
const msg = { url: job.msg };
try {
const connection = await amqp.connect(Rabbitmq.host);
const channel = await connection.createChannel();
await channel.assertQueue(queue, { durable: true });
await channel.sendToQueue(queue, Buffer.from(JSON.stringify(msg)));
await channel.close();
await connection.close();
} catch (error) {
logger.error(
"[AMQP] Error publishing to RabbitMQ",
job.msg,
"queue name:",
job.queueName
);
}
};
This function is called in a for loop to publish an array of messages.
When all the messages are published, I have many many connections and channels open... while it should be closed thanks to channel.close and connection.close ... but it's not...
I check a lot on other posts, but I didn't succeed so far...
Where am I wrong ?

Related

What could be the reasons for not receiving messages in RabbitMQ?

This is my first time testing RabbitMQ with node.js, and I utilized amqplib.
First, I run the
node ./messages/consumer.js
and out put as follow -:
Connected to RabbitMQ
Channel created
Waiting for messages...
Second, I run the
node ./messages/producer.js
and out put as follow -:
Connected to RabbitMQ
Channel created
Message sent: Hello, world!
Connection to RabbitMQ closed
From the RabbitMQ management console, I observed the presence of test_exchange, test_queue, and test_key, but there was no information regarding any messages. And the consumer terminal did not log any indication of receiving a message. It still displays the message "Waiting for message.". Could you kindly inform me of where I may have overlooked this information?
//config.js
module.exports = {
rabbitmq: {
host: 'localhost',
port: 5672,
username: 'guest',
password: 'guest',
vhost: '/',
exchange: 'test_exchange',
queue: 'test_queue',
routingKey: 'test_key'
}
}
//rabbitmq.js
const amqp = require("amqplib");
const config = require("../config/config");
class RabbitMQ {
constructor() {
this.connection = null;
this.channel = null;
}
async connect() {
try {
const { host, port, username, password, vhost } = config.rabbitmq;
this.connection = await amqp.connect(
`amqp://${username}:${password}#${host}:${port}/${vhost}`
);
console.log("Connected to RabbitMQ");
return this.connection;
} catch (error) {
console.error("Error connecting to RabbitMQ", error);
}
}
async createChannel() {
try {
if (!this.connection) {
await this.connect();
}
this.channel = await this.connection.createChannel();
console.log("Channel created");
return this.channel;
} catch (error) {
console.error("Error creating channel", error);
}
}
async close() {
try {
await this.connection.close();
console.log("Connection to RabbitMQ closed");
} catch (error) {
console.error("Error closing connection to RabbitMQ", error);
}
}
}
module.exports = new RabbitMQ();
//producer.js
const rabbitmq = require('../lib/rabbitmq');
const config = require('../config/config');
async function produceMessage(message) {
try {
const channel = await rabbitmq.createChannel();
const exchange = config.rabbitmq.exchange;
const queue = config.rabbitmq.queue;
const key = config.rabbitmq.routingKey;
await channel.assertExchange(exchange, 'direct', { durable: true });
await channel.assertQueue(queue, { durable: true });
await channel.bindQueue(queue, exchange, key);
const messageBuffer = Buffer.from(message);
await channel.publish(exchange, key, messageBuffer);
console.log(`Message sent: ${message}`);
await rabbitmq.close();
} catch (error) {
console.error('Error producing message', error);
}
}
produceMessage('Hello, world!');
//consumer.js
const rabbitmq = require('../lib/rabbitmq');
const config = require('../config/config');
async function consumeMessage() {
try {
const channel = await rabbitmq.createChannel();
const exchange = config.rabbitmq.exchange;
const queue = config.rabbitmq.queue;
const key = config.rabbitmq.routingKey;
await channel.assertExchange(exchange, 'direct', { durable: true });
await channel.assertQueue(queue, { durable: true });
await channel.bindQueue(queue, exchange, key);
channel.consume(queue, (msg) => {
console.log(`Message received: ${msg.content.toString()}`);
channel.ack(msg);
}, { noAck: false });
console.log('Waiting for messages...');
} catch (error) {
console.error('Error consuming message', error);
}
}
consumeMessage();
Problem:
Your message is failing to send because you are closing the connection immediately after executing the publish command. You can try this by commenting the line await rabbitmq.close(); in producer.js.
Solution:
If you want to close the connection after sending the message. You can create confirm channel instead of a normal channel, which will allow you to receive send acknowledgment.
1. Channel Creation
Change the channel creation line in rabbitmq.js file:
this.channel = await this.connection.createConfirmChannel();
2. Producer:
In the producer.js, call waitForConfirms function before closing the connection:
await channel.waitForConfirms();
await rabbitmq.close();

SlackBot repeat the event without control

I am trying to create a bot in Slack called "Chochon", the problem is that when I receive the event "app_mention", chochon responds more than once several times in a row, instead of sending 1 message and stopping until they mention it again.
This is my code, chochon function:
socketModeClient.on('app_mention', async ({ event }) => {
try {
console.log(event);
let userBox = await Chochon.users.info({ user: event.user });
let userProfile = userBox.user.profile;
console.log(cli.green(`Event received : [ ${event.type} ] from [ ${userProfile.display_name} ]`));
// Respond to the event
Chochon.chat.postMessage({
channel: event.channel,
text: `Hello <#${event.user}>, I'm Chochon!, I'm a bot that can help you to manage your team.`
});
} catch (error) {
console.error(error);
}
});
The slack client:
Full code:
// Dependencies :
const dotenv = require('dotenv').config();
const path = require('path');
const cli = require('cli-color');
// Web client [CLI]
const { WebClient } = require('#slack/web-api');
const Chochon = new WebClient(process.env.SLACK_BOT_TOKEN.trim());
// Socket IO
const { SocketModeClient } = require('#slack/socket-mode');
const appToken = process.env.SLACK_APP_TOKEN;
const socketModeClient = new SocketModeClient({ appToken });
socketModeClient.start();
// Internal functions
//const eventManager = require(path.resolve(__dirname, './utils/events/manager'));
socketModeClient.on('app_mention', async ({ event }) => {
try {
console.log(event);
let userBox = await Chochon.users.info({ user: event.user });
let userProfile = userBox.user.profile;
console.log(cli.green(`Event received : [ ${event.type} ] from [ ${userProfile.display_name} ]`));
// Respond to the event
Chochon.chat.postMessage({
channel: event.channel,
text: `Hello <#${event.user}>, I'm Chochon!, I'm a bot that can help you to manage your team.`
});
} catch (error) {
console.error(error);
}
});
socketModeClient.on('slash_commands', async ({ body, ack }) => {
if (body.command === "/ping") {
console.log(cli.green(`Event received : [ ${body.command} ]`));
await ack({"text": "I got it, pong!"});
}
});

RabbitMQ HeartBeat Timeout issue

I'm currently using RabbitMQ as a message broker. Recently, I see many error HeartBeat Timeout in my error log.
Also in RabbitMQ log, I see this log:
I don't know why there is too many connection from vary ranges of port. I use default setup without any further configuration.
Here is my code used to publish and consume:
import { connect } from 'amqplib/callback_api';
import hanlder from '../calculator/middleware';
import { logger } from '../config/logger';
async function consumeRabbitMQServer(serverURL, exchange, queue) {
connect('amqp://localhost', async (error0, connection) => {
if (error0) throw error0;
const channel = connection.createChannel((error1) => {
if (error1) throw error1;
});
channel.assertExchange(exchange, 'direct', {
durable: true
});
channel.assertQueue(
queue,
{
durable: true
},
(error2) => {
if (error2) throw error2;
logger.info(`Connect to ${serverURL} using queue ${queue}`);
}
);
channel.prefetch(1);
channel.bindQueue(queue, exchange, 'info');
channel.noAck = true;
channel.consume(queue, (msg) => {
hanlder(JSON.parse(msg.content.toString()))
.then(() => {
channel.ack(msg);
})
.catch((err) => {
channel.reject(msg);
});
});
});
}
export default consumeRabbitMQServer;
Code used to publish message:
import createConnection from './connection';
import { logger } from '../config/logger';
async function publishToRabbitMQServer(serverURL, exchange, queue) {
const connection = createConnection(serverURL);
const c = await connection.then(async (conn) => {
const channel = await conn.createChannel((error1) => {
if (error1) throw error1;
});
channel.assertExchange(exchange, 'direct', {
durable: true
});
channel.assertQueue(
queue,
{
durable: true
},
(error2) => {
if (error2) throw error2;
logger.info(`Publish to ${serverURL} using queue ${queue}`);
}
);
channel.bindQueue(queue, exchange, 'info');
return channel;
});
return c;
}
export default publishToRabbitMQServer;
Whenever I start my server, I run this piece of code to create a client consume to RabbitMQ:
const { RABBITMQ_SERVER } = process.env;
consumeRabbitMQServer(RABBITMQ_SERVER, 'abc', 'abc');
And this piece of code is used when ever a message in need published to RabbitMQ
const payloads = call.request.payloads;
const { RABBITMQ_SERVER } = process.env;
const channel = await publishToRabbitMQServer(RABBITMQ_SERVER, 'abc', 'abc');
for (let i = 0; i < payloads.length; i++) {
channel.publish('abc', 'info', Buffer.from(JSON.stringify(payloads[i])));
}
I'm reusing code from RabbitMQ document, and it seem that this problem happen whenever there are too many user publish message. Thanks for helping.
Update: I think the root cause is when I need to publish a message, I create a new connection. I'm working to improve it, any help is appreciate. Many thanks.

AMQP + NodeJS wait for channel

I have a service in FeathersJS that initiates a connection to RabbitMQ, the issue is how to await for a channel to be ready before receiving requests:
class Service {
constructor({ amqpConnection, queueName }) {
this.amqpConnection = amqpConnection;
this.queueName = queueName;
this.replyQueueName = queueName + "Reply"
}
async create(data, params) {
new Promise(resolve => {
if (!this.channel) await this.createChannel();
channel.responseEmitter.once(correlationId, resolve);
channel.sendToQueue(this.queueName, Buffer.from(data), {
correlationId: asyncLocalStorage.getStore(),
replyTo: this.replyQueueName,
});
});
}
async createChannel() {
let connection = this.amqpConnection();
let channel = await connection.createChannel();
await channel.assertQueue(this.queueName, {
durable: false,
});
this.channel = channel;
channel.responseEmitter = new EventEmitter();
channel.responseEmitter.setMaxListeners(0);
channel.consume(
this.replyQueueName,
(msg) => {
channel.responseEmitter.emit(
msg.properties.correlationId,
msg.content.toString("utf8")
);
},
{ noAck: true }
);
}
....
}
Waiting for the channel to be created during a request seems like a waste. How should this be done "correctly"?
Feathers services can implement a setup method which will be called when the server is started (or you call app.setup() yourself):
class Service {
async setup () {
await this.createChannel();
}
}

Logger callback and Router requests causing SQL Server concurrency issues in Express

I have a express server that handles queries on a SQL Server server database. For my logs, I am logging all requests that are received into a logs table:
const morgan = require('morgan')
const stream = require('stream')
const DatabaseController = require('./dbcontroller')
const databaseStream = new stream.Writable()
// anything that is logged is passed to this stream... the text is logged
databaseStream.write = async (text) => {
await DatabaseController.addLog({ text })
}
// remove the img binary from the body - which is too large
const logger = morgan( 'tiny', { immediate: true, stream: databaseStream })
module.exports = logger
Additionally, there are some requests that can be made that will also make connections on the database.
router.get('/logs', async (req, res, next) => {
try {
const logdata = await DatabaseController.getLogs()
res.status(200)
res.send({ logdata })
} catch (err) { next(err) }
})
Normal requests are fine. However anytime I make a request that requires connecting to the database (GET/logs). I get the following:
Global connection already exists. Call sql.close() first.
DatabaseController implementation:
const sql = require('mssql')
const config = require('config')
const dconfig = config.database
const addLog = async (options) => {
const { text, isError } = options
try {
let pool = await sql.connect(dconfig)
let request = await pool.request()
request.input('message', sql.Text, text.trim())
if (isError) {
request.input('iserr', sql.Bit, isError)
await request.query('insert into dbo.MApp_Logs (message, is_error) values (#message, #iserr)')
} else {
await request.query('insert into dbo.MApp_Logs (message) values (#message)')
}
} catch (err) {
throw err
} finally {
await sql.close()
}
}
const getLogs = async () => {
try {
let pool = await sql.connect(dconfig)
return await pool.request().query('SELECT * FROM dbo.MApp_Logs')
} catch (err) {
throw err
} finally {
await sql.close()
}
}
The issue is that as is. I can't prevent my code from attempting to make multiple connections. Using a connection pool gives me the following error:
"message": "Already connecting to database! Call close before connecting to different database.",
"code": "EALREADYCONNECTING"
How can I get around this issue? Is it possible to wait for a connection to close or use the existing connection?

Resources