UNPKG

redis-smq

Version:

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

187 lines 11.1 kB
"use strict"; 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