redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
97 lines • 3.6 kB
JavaScript
import { CallbackEmptyReplyError, Runnable, } from 'redis-smq-common';
import { _executeAcknowledgementScript } from './_/_execute-acknowledgement-script.js';
export class MessageAcknowledger extends Runnable {
pending = [];
timer = null;
pendingCallbacks = [];
consumerId;
queue;
logger;
useBatchAcks;
batchSize;
batchTimeoutMs;
constructor(queue, consumerId, logger, consumerOptions) {
super();
this.queue = queue;
this.consumerId = consumerId;
this.logger = logger.createLogger(this.constructor.name);
const { enabled, batchTimeoutMs, batchSize } = consumerOptions.batchAcks;
this.useBatchAcks = enabled;
this.batchSize = batchSize;
this.batchTimeoutMs = batchTimeoutMs;
}
add(message) {
const messageId = message.getId();
if (!this.isOperational()) {
this.logger.warn(`Rejecting message ${messageId} - MessageAcknowledger is not operational`);
return;
}
if (!this.useBatchAcks) {
const messages = [message];
_executeAcknowledgementScript(this.queue, this.consumerId, messages, (err, result) => this.handleAcknowledgementResult(err, result, messages));
return;
}
this.pending.push(message);
this.logger.debug(`Queued message ${messageId} for batch. Queue size: ${this.pending.length}`);
if (this.pending.length >= this.batchSize) {
this.flush();
}
else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.batchTimeoutMs);
}
}
flush(cb) {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
if (this.pending.length === 0) {
if (cb)
cb();
return;
}
if (cb)
this.pendingCallbacks.push(cb);
const batch = [...this.pending];
this.pending = [];
this.logger.debug(`Flushing batch of ${batch.length} acknowledgments`);
_executeAcknowledgementScript(this.queue, this.consumerId, batch, (err, result) => {
this.handleAcknowledgementResult(err, result, batch);
const callbacks = [...this.pendingCallbacks];
this.pendingCallbacks = [];
callbacks.forEach((callback) => callback());
});
}
goingDown() {
return [
(cb) => {
this.logger.info(`Shutting down with ${this.pending.length} pending acknowledgments`);
this.flush(cb);
},
];
}
handleAcknowledgementResult(err, result, messages) {
if (err) {
this.logger.error(`Message acknowledgement failed due to: ${err}`);
this.handleError(err);
return;
}
if (!result) {
this.handleError(new CallbackEmptyReplyError({
message: `_executeAcknowledgementScript() returned an empty result.`,
}));
return;
}
result.forEach((result, index) => {
const message = messages[index];
const messageId = message.getId();
if (result === 0) {
this.logger.info(`Message ${messageId} was already acknowledged or something else`);
return;
}
this.logger.debug(`Message ${messageId} acknowledged successfully`);
this.emit('messageAcknowledger.messageAcknowledged', message);
});
}
}
//# sourceMappingURL=message-acknowledger.js.map