redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
166 lines • 6.98 kB
JavaScript
import { CallbackEmptyReplyError, PanicError, Runnable, } from 'redis-smq-common';
import { redisKeys } from '../../../common/redis/redis-keys/redis-keys.js';
import { _hasRateLimitExceeded } from '../../../queue-rate-limit/_/_has-rate-limit-exceeded.js';
import { _getQueueProperties } from '../../../queue-manager/_/_get-queue-properties.js';
import { EQueueType, } from '../../../index.js';
import { eventPublisher } from './event-publisher.js';
import { ERedisConnectionAcquisitionMode } from '../../../common/redis/redis-connection-pool/types/connection-pool.js';
import { RedisConnectionPool } from '../../../common/redis/redis-connection-pool/redis-connection-pool.js';
export class DequeueMessage extends Runnable {
consumerContext;
config;
logger;
queue;
keyQueues;
keyQueueConsumers;
keyConsumerQueues;
keyQueueProcessingQueues;
keyQueueProcessing;
keyQueuePending;
keyQueuePriorityPending;
keyQueueProperties;
blockUntilMessageReceived;
autoCloseRedisConnection;
redisClient = null;
queueRateLimit = null;
queueType = null;
constructor(consumerContext, queue, blockUntilMessageReceived = true, autoCloseRedisConnection = true) {
super();
this.consumerContext = consumerContext;
this.logger = consumerContext.logger.createLogger(this.constructor.name);
this.config = consumerContext.config;
this.queue = queue;
this.blockUntilMessageReceived = blockUntilMessageReceived;
this.autoCloseRedisConnection = autoCloseRedisConnection;
eventPublisher(this);
const { keyConsumerQueues } = redisKeys.getConsumerKeys(this.consumerContext.consumerId);
const { keyQueueProcessing } = redisKeys.getQueueConsumerKeys(this.queue.queueParams, this.consumerContext.consumerId);
const { keyQueues } = redisKeys.getMainKeys();
const { keyQueueProcessingQueues, keyQueuePending, keyQueuePriority, keyQueueConsumers, keyQueueProperties, } = redisKeys.getQueueKeys(this.queue.queueParams.ns, this.queue.queueParams.name, this.queue.groupId);
this.keyQueueProperties = keyQueueProperties;
this.keyQueuePriorityPending = keyQueuePriority;
this.keyQueuePending = keyQueuePending;
this.keyQueueProcessing = keyQueueProcessing;
this.keyQueues = keyQueues;
this.keyQueueConsumers = keyQueueConsumers;
this.keyConsumerQueues = keyConsumerQueues;
this.keyQueueProcessingQueues = keyQueueProcessingQueues;
}
runDequeue(redisClient) {
if (this.queueRateLimit) {
_hasRateLimitExceeded(redisClient, this.queue.queueParams, this.queueRateLimit, (err, isExceeded) => {
if (err)
this.handleError(err);
else if (isExceeded) {
this.emit('consumer.dequeueMessage.nextMessage');
}
else {
this.performDequeue(redisClient);
}
});
}
else {
this.performDequeue(redisClient);
}
}
performDequeue(redisClient) {
if (this.isPriorityQueuingEnabled()) {
redisClient.zpoplpush(this.keyQueuePriorityPending, this.keyQueueProcessing, this.handleMessage);
}
else if (this.blockUntilMessageReceived && !this.queueRateLimit) {
redisClient.brpoplpush(this.keyQueuePending, this.keyQueueProcessing, 0, this.handleMessage);
}
else {
redisClient.rpoplpush(this.keyQueuePending, this.keyQueueProcessing, this.handleMessage);
}
}
getRedisClient() {
if (!this.redisClient)
return new PanicError({ message: 'A RedisClient instance is required.' });
return this.redisClient;
}
handleError(err) {
if (!this.isOperational())
return;
this.logger.error(`DequeueMessage error: ${err.message}`, err);
this.emit('consumer.dequeueMessage.error', err, this.consumerContext.consumerId, this.queue);
super.handleError(err);
}
goingUp() {
return super.goingUp().concat([
(cb) => {
const redisConnectionAcquisitionMode = this.blockUntilMessageReceived
? ERedisConnectionAcquisitionMode.EXCLUSIVE
: ERedisConnectionAcquisitionMode.SHARED;
RedisConnectionPool.getInstance().acquire(redisConnectionAcquisitionMode, (err, redisClient) => {
if (err)
return cb(err);
if (!redisClient)
return cb(new CallbackEmptyReplyError());
this.redisClient = redisClient;
cb();
});
},
(cb) => {
const redisClient = this.getRedisClient();
if (redisClient instanceof Error)
return cb(redisClient);
_getQueueProperties(redisClient, this.queue.queueParams, (err, queueProperties) => {
if (err)
return cb(err);
if (!queueProperties)
return cb(new CallbackEmptyReplyError());
this.queueType = queueProperties.queueType;
this.queueRateLimit = queueProperties.rateLimit ?? null;
cb();
});
},
]);
}
goingDown() {
return [
(cb) => {
if (this.redisClient) {
if (this.autoCloseRedisConnection) {
RedisConnectionPool.getInstance().destroy(this.redisClient, () => {
this.redisClient = null;
cb();
});
}
else {
RedisConnectionPool.getInstance().release(this.redisClient);
this.redisClient = null;
cb();
}
}
else
cb();
},
].concat(super.goingDown());
}
isPriorityQueuingEnabled() {
return this.queueType === EQueueType.PRIORITY_QUEUE;
}
handleMessage = (err, messageId) => {
if (err) {
this.handleError(err);
}
else if (typeof messageId === 'string') {
this.emit('consumer.dequeueMessage.messageReceived', messageId, this.queue, this.consumerContext.consumerId);
}
else {
this.emit('consumer.dequeueMessage.nextMessage');
}
};
dequeue() {
if (!this.isOperational()) {
return;
}
const redisClient = this.getRedisClient();
if (redisClient instanceof Error) {
return this.handleError(redisClient);
}
this.runDequeue(redisClient);
}
}
//# sourceMappingURL=dequeue-message.js.map