UNPKG

redis-smq

Version:

A high-performance, reliable, and scalable message queue for Node.js.

116 lines 6.25 kB
import { async } from 'redis-smq-common'; import { _getQueueConsumerIds } from '../../../../queue-manager/_/_get-queue-consumer-ids.js'; import { MessageUnacknowledger } from '../../consume-message/message-unacknowledger.js'; import { EMessageUnacknowledgementCause } from '../../consume-message/types/index.js'; import { withSharedPoolConnection } from '../../../../common/redis/redis-connection-pool/with-shared-pool-connection.js'; import { _deleteEphemeralConsumerGroup } from '../../_/_delete-ephemeral-consumer-group.js'; import { QueueWorkerAbstract } from '../queue-worker-abstract.js'; import { _isConsumerAlive } from '../../../_/_is-consumer-alive.js'; import { Consumer } from '../../../consumer.js'; import { _unsubscribeConsumer } from '../../_/_unsubscribe-consumer.js'; export class ReapConsumersWorker extends QueueWorkerAbstract { createMessageUnacknowledger(consumerId) { const unacknowledger = new MessageUnacknowledger(consumerId, this.queueParsedParams, this.logger, Consumer.getDefaultOptions()); unacknowledger.on('messageUnacknowledger.error', (err) => { this.logger.error(`MessageUnacknowledger error for consumer ${consumerId}:`, err); }); unacknowledger.on('messageUnacknowledger.messagesUnacknowledged', (status) => { const messageCount = Object.keys(status).length; if (messageCount > 0) { this.logger.info(`Unacknowledged ${messageCount} messages for consumer ${consumerId}`); } }); return unacknowledger; } recoverConsumer(consumerId, unacknowledger, cb) { const queue = this.queueParsedParams.queueParams; const queueRef = `${queue.name}@${queue.ns}`; async.series([ (done) => { this.logger.debug(`Starting unacknowledger for consumer ${consumerId}`); unacknowledger.run(done); }, (done) => { this.logger.debug(`Unacknowledging messages for consumer ${consumerId} on queue ${queueRef}`); unacknowledger.unacknowledgeProcessingQueue(EMessageUnacknowledgementCause.OFFLINE_CONSUMER, (err, status) => { if (err) { this.logger.error(`Failed to unacknowledge messages for consumer ${consumerId}: ${err.message}`); return done(); } const count = status ? Object.keys(status).length : 0; if (count > 0) { this.logger.info(`Unacknowledged ${count} messages for consumer ${consumerId}`); } done(); }); }, (done) => { this.logger.debug(`Cleaning up ephemeral groups for consumer ${consumerId}`); _deleteEphemeralConsumerGroup(queue, consumerId, null, (err) => { if (err) { this.logger.warn(`Failed to delete ephemeral group for ${queueRef}, consumer ${consumerId}: ${err.message}`); } else { this.logger.debug(`Ephemeral group deleted for ${queueRef}, consumer ${consumerId}`); } done(); }); }, (done) => { _unsubscribeConsumer(consumerId, this.queueParsedParams, (err) => { if (err) { const queue = this.queueParsedParams.queueParams; this.logger.error(`Failed to unsubscribe consumer ${consumerId} from queue ${queue.name}@${queue.ns}: ${err.message}`); } done(err); }); }, ], (err) => { this.logger.debug(`Shutting down unacknowledger for consumer ${consumerId}`); unacknowledger.shutdown(() => cb(err)); }); } work = (cb) => { withSharedPoolConnection((redisClient, connCb) => { const queue = this.queueParsedParams.queueParams; const queueRef = `${queue.name}@${queue.ns}`; _getQueueConsumerIds(redisClient, queue, (err, consumerIds) => { if (err) { this.logger.error(`Failed to get consumer IDs for ${queueRef}: ${err.message}`); return connCb(err); } const consumers = (consumerIds || []).filter((i) => i !== this.consumerId); if (consumers.length === 0) { return connCb(); } this.logger.debug(`Found ${consumers.length} consumers for queue ${queueRef}`); async.eachOf(consumers, (consumerId, index, done) => { this.logger.debug(`[${index + 1}/${consumers.length}] Checking heartbeat for consumer ${consumerId}`); _isConsumerAlive(redisClient, consumerId, (err, alive) => { if (err) { this.logger.error(`Heartbeat check failed for ${consumerId}: ${err.message}`); return done(err); } if (alive) { this.logger.debug(`Consumer ${consumerId} is alive`); return done(); } this.logger.info(`Consumer ${consumerId} is offline, starting recovery`); const unacknowledger = this.createMessageUnacknowledger(consumerId); this.recoverConsumer(consumerId, unacknowledger, (recoveryErr) => { if (recoveryErr) { this.logger.error(`Recovery failed for consumer ${consumerId}: ${recoveryErr.message}`); } else { this.logger.info(`Successfully recovered consumer ${consumerId}`); } done(); }); }); }, connCb); }); }, cb); }; } export default ReapConsumersWorker; //# sourceMappingURL=reap-consumers.worker.js.map