redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
116 lines • 6.25 kB
JavaScript
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