UNPKG

ioredis

Version:

A robust, performance-focused and full-featured Redis client for Node.js.

120 lines (119 loc) 4.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const PromiseContainer = require("./promiseContainer"); const calculateSlot = require("cluster-key-slot"); const standard_as_callback_1 = require("standard-as-callback"); exports.kExec = Symbol("exec"); exports.kCallbacks = Symbol("callbacks"); exports.notAllowedAutoPipelineCommands = [ "auth", "info", "script", "quit", "cluster", "pipeline", "multi", "subscribe", "psubscribe", "unsubscribe", "unpsubscribe", ]; function findAutoPipeline(client, _commandName, ...args) { if (!client.isCluster) { return "main"; } // We have slot information, we can improve routing by grouping slots served by the same subset of nodes return client.slots[calculateSlot(args[0])].join(","); } function executeAutoPipeline(client, slotKey) { /* If a pipeline is already executing, keep queueing up commands since ioredis won't serve two pipelines at the same time */ if (client._runningAutoPipelines.has(slotKey)) { return; } client._runningAutoPipelines.add(slotKey); // Get the pipeline and immediately delete it so that new commands are queued on a new pipeline const pipeline = client._autoPipelines.get(slotKey); client._autoPipelines.delete(slotKey); const callbacks = pipeline[exports.kCallbacks]; // Perform the call pipeline.exec(function (err, results) { client._runningAutoPipelines.delete(slotKey); /* Invoke all callback in nextTick so the stack is cleared and callbacks can throw errors without affecting other callbacks. */ if (err) { for (let i = 0; i < callbacks.length; i++) { process.nextTick(callbacks[i], err); } } else { for (let i = 0; i < callbacks.length; i++) { process.nextTick(callbacks[i], ...results[i]); } } // If there is another pipeline on the same node, immediately execute it without waiting for nextTick if (client._autoPipelines.has(slotKey)) { executeAutoPipeline(client, slotKey); } }); } function shouldUseAutoPipelining(client, commandName) { return (client.options.enableAutoPipelining && !client.isPipeline && !exports.notAllowedAutoPipelineCommands.includes(commandName) && !client.options.autoPipeliningIgnoredCommands.includes(commandName)); } exports.shouldUseAutoPipelining = shouldUseAutoPipelining; function executeWithAutoPipelining(client, commandName, args, callback) { const CustomPromise = PromiseContainer.get(); // On cluster mode let's wait for slots to be available if (client.isCluster && !client.slots.length) { return new CustomPromise(function (resolve, reject) { client.delayUntilReady((err) => { if (err) { reject(err); return; } executeWithAutoPipelining(client, commandName, args, callback).then(resolve, reject); }); }); } const slotKey = findAutoPipeline(client, commandName, ...args); if (!client._autoPipelines.has(slotKey)) { const pipeline = client.pipeline(); pipeline[exports.kExec] = false; pipeline[exports.kCallbacks] = []; client._autoPipelines.set(slotKey, pipeline); } const pipeline = client._autoPipelines.get(slotKey); /* Mark the pipeline as scheduled. The symbol will make sure that the pipeline is only scheduled once per tick. New commands are appended to an already scheduled pipeline. */ if (!pipeline[exports.kExec]) { pipeline[exports.kExec] = true; /* Deferring with setImmediate so we have a chance to capture multiple commands that can be scheduled by I/O events already in the event loop queue. */ setImmediate(executeAutoPipeline, client, slotKey); } // Create the promise which will execute the const autoPipelinePromise = new CustomPromise(function (resolve, reject) { pipeline[exports.kCallbacks].push(function (err, value) { if (err) { reject(err); return; } resolve(value); }); pipeline[commandName](...args); }); return standard_as_callback_1.default(autoPipelinePromise, callback); } exports.executeWithAutoPipelining = executeWithAutoPipelining;