redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
187 lines • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueueRateLimit = void 0;
const redis_smq_common_1 = require("redis-smq-common");
const scripts_js_1 = require("../common/redis/scripts.js");
const redis_keys_js_1 = require("../common/redis/redis-keys/redis-keys.js");
const configuration_js_1 = require("../config-manager/configuration.js");
const _parse_queue_params_and_validate_js_1 = require("../queue-manager/_/_parse-queue-params-and-validate.js");
const index_js_1 = require("../queue-manager/index.js");
const _has_rate_limit_exceeded_js_1 = require("./_/_has-rate-limit-exceeded.js");
const index_js_2 = require("../errors/index.js");
const with_shared_pool_connection_js_1 = require("../common/redis/redis-connection-pool/with-shared-pool-connection.js");
const _parse_queue_params_js_1 = require("../queue-manager/_/_parse-queue-params.js");
const get_redis_for_queue_operation_js_1 = require("../common/helpers/get-redis-for-queue-operation.js");
const index_js_3 = require("../queue-operation-validator/index.js");
class QueueRateLimit {
constructor() {
this.logger = (0, redis_smq_common_1.createLogger)(configuration_js_1.Configuration.getConfig().logger, this.constructor.name.toLowerCase());
this.queue = new index_js_1.QueueManager();
this.logger.debug('QueueRateLimit initialized');
}
clear(queue, cb) {
return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Clearing rate limit for queue: ${queueName}`);
const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue);
if (queueParams instanceof Error)
return callback(queueParams);
(0, get_redis_for_queue_operation_js_1.getRedisForQueueOperation)(queue, index_js_3.EQueueOperation.CLEAR_RATE_LIMIT, (client, cb) => {
const { keyQueueProperties, keyQueueRateLimit } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams.ns, queueParams.name, null);
const argv = [
index_js_1.EQueueProperty.RATE_LIMIT,
index_js_1.EQueueProperty.OPERATIONAL_STATE,
index_js_1.EQueueOperationalState.LOCKED,
index_js_1.EQueueProperty.LOCK_ID,
'',
];
client.runScript(scripts_js_1.ERedisScriptName.CLEAR_QUEUE_RATE_LIMIT, [keyQueueProperties, keyQueueRateLimit], argv, (err, reply) => {
if (err) {
this.logger.error(`Failed to clear rate limit for ${queueName}: ${err.message}`);
return cb(err);
}
const replyStr = String(reply);
if (replyStr === 'QUEUE_LOCKED') {
const error = new index_js_2.QueueLockedError({
metadata: { queue: queueParams },
});
this.logger.error(`Cannot clear rate limit for ${queueName}: Queue is locked and no lock ID provided`);
return cb(error);
}
if (replyStr === 'QUEUE_NOT_FOUND') {
const error = new index_js_2.QueueNotFoundError({
metadata: { queue: queueParams },
});
this.logger.error(`Queue not found when clearing rate limit: ${queueName}`);
return cb(error);
}
if (replyStr !== 'OK') {
const error = new index_js_2.UnexpectedScriptReplyError({
metadata: { reply },
});
this.logger.error(`Unexpected reply when clearing rate limit for ${queueName}: ${replyStr}`);
return cb(error);
}
this.logger.info(`Cleared rate limit for queue ${queueParams.name}@${queueParams.ns}`);
cb();
});
}, callback);
});
}
set(queue, rateLimit, cb) {
return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Setting rate limit for ${queueName}: ${rateLimit.limit}/${rateLimit.interval}ms`);
(0, get_redis_for_queue_operation_js_1.getRedisForQueueOperation)(queue, index_js_3.EQueueOperation.SET_RATE_LIMIT, (client, cb) => {
const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue);
if (queueParams instanceof Error)
return cb(queueParams);
const limit = Number(rateLimit.limit);
if (isNaN(limit) || limit <= 0) {
const error = new index_js_2.InvalidRateLimitValueError();
this.logger.error(`Invalid rate limit value for ${queueName}: ${rateLimit.limit}`);
return cb(error);
}
const interval = Number(rateLimit.interval);
if (isNaN(interval) || interval < 1000) {
const error = new index_js_2.InvalidRateLimitIntervalError();
this.logger.error(`Invalid rate limit interval for ${queueName}: ${rateLimit.interval}`);
return cb(error);
}
const validatedRateLimit = { interval, limit };
const { keyQueueProperties } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams.ns, queueParams.name, null);
const argv = [
index_js_1.EQueueProperty.RATE_LIMIT,
JSON.stringify(validatedRateLimit),
index_js_1.EQueueProperty.OPERATIONAL_STATE,
index_js_1.EQueueOperationalState.LOCKED,
index_js_1.EQueueProperty.LOCK_ID,
'',
];
client.runScript(scripts_js_1.ERedisScriptName.SET_QUEUE_RATE_LIMIT, [keyQueueProperties], argv, (err, reply) => {
if (err) {
this.logger.error(`Failed to set rate limit for ${queueName}: ${err.message}`);
return cb(err);
}
const replyStr = String(reply);
if (replyStr === 'QUEUE_LOCKED') {
const error = new index_js_2.QueueLockedError({
metadata: { queue: queueParams },
});
this.logger.error(`Cannot set rate limit for ${queueName}: Queue is locked and no lock ID provided`);
return cb(error);
}
if (replyStr === 'QUEUE_NOT_FOUND') {
const error = new index_js_2.QueueNotFoundError({
metadata: { queue: queueParams },
});
this.logger.error(`Queue not found when setting rate limit: ${queueName}`);
return cb(error);
}
if (replyStr !== 'OK') {
const error = new index_js_2.UnexpectedScriptReplyError({
metadata: { reply },
});
this.logger.error(`Unexpected reply when setting rate limit for ${queueName}: ${replyStr}`);
return cb(error);
}
this.logger.info(`Set rate limit for ${queueParams.name}@${queueParams.ns}: ${limit}/${interval}ms`);
cb();
});
}, callback);
});
}
hasExceeded(queue, rateLimit, cb) {
return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Checking rate limit for ${queueName}: ${rateLimit.limit}/${rateLimit.interval}ms`);
(0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => {
(0, _parse_queue_params_and_validate_js_1._parseQueueParamsAndValidate)(client, queue, (err, queueParams) => {
if (err) {
this.logger.error(`Failed to validate queue ${queueName}: ${err.message}`);
return cb(err);
}
if (!queueParams) {
const error = new redis_smq_common_1.CallbackEmptyReplyError();
this.logger.error(`Empty queue parameters for ${queueName}`);
return cb(error);
}
(0, _has_rate_limit_exceeded_js_1._hasRateLimitExceeded)(client, queueParams, rateLimit, (err, hasExceeded) => {
if (err) {
this.logger.error(`Failed to check rate limit for ${queueName}: ${err.message}`);
return cb(err);
}
this.logger.debug(`Rate limit ${hasExceeded ? 'exceeded' : 'not exceeded'} for ${queueName}`);
cb(null, hasExceeded);
});
});
}, callback);
});
}
get(queue, cb) {
return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Getting rate limit for ${queueName}`);
(0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => {
redis_smq_common_1.async.withCallback((cb) => (0, _parse_queue_params_and_validate_js_1._parseQueueParamsAndValidate)(client, queue, cb), (queueParams, cb) => {
const { keyQueueProperties } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams.ns, queueParams.name, null);
client.hget(keyQueueProperties, String(index_js_1.EQueueProperty.RATE_LIMIT), (err, reply) => {
if (err) {
this.logger.error(`Failed to get rate limit for ${queueName}: ${err.message}`);
return cb(err);
}
if (!reply) {
this.logger.debug(`No rate limit found for ${queueName}`);
return cb(null, null);
}
const rateLimit = JSON.parse(reply);
this.logger.debug(`Got rate limit for ${queueName}: ${rateLimit.limit}/${rateLimit.interval}ms`);
cb(null, rateLimit);
});
}, cb);
}, callback);
});
}
}
exports.QueueRateLimit = QueueRateLimit;
//# sourceMappingURL=queue-rate-limit.js.map