pipeline.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const command_1 = require("./command");
  4. const util_1 = require("util");
  5. const standard_as_callback_1 = require("standard-as-callback");
  6. const redis_commands_1 = require("redis-commands");
  7. const cluster_key_slot_1 = require("cluster-key-slot");
  8. const PromiseContainer = require("./promiseContainer");
  9. const commander_1 = require("./commander");
  10. function Pipeline(redis) {
  11. commander_1.default.call(this);
  12. this.redis = redis;
  13. this.isCluster = this.redis.constructor.name === "Cluster";
  14. this.options = redis.options;
  15. this._queue = [];
  16. this._result = [];
  17. this._transactions = 0;
  18. this._shaToScript = {};
  19. Object.keys(redis.scriptsSet).forEach(name => {
  20. const script = redis.scriptsSet[name];
  21. this._shaToScript[script.sha] = script;
  22. this[name] = redis[name];
  23. this[name + "Buffer"] = redis[name + "Buffer"];
  24. });
  25. const Promise = PromiseContainer.get();
  26. this.promise = new Promise((resolve, reject) => {
  27. this.resolve = resolve;
  28. this.reject = reject;
  29. });
  30. const _this = this;
  31. Object.defineProperty(this, "length", {
  32. get: function () {
  33. return _this._queue.length;
  34. }
  35. });
  36. }
  37. exports.default = Pipeline;
  38. Object.assign(Pipeline.prototype, commander_1.default.prototype);
  39. Pipeline.prototype.fillResult = function (value, position) {
  40. if (this._queue[position].name === "exec" && Array.isArray(value[1])) {
  41. var execLength = value[1].length;
  42. for (let i = 0; i < execLength; i++) {
  43. if (value[1][i] instanceof Error) {
  44. continue;
  45. }
  46. const cmd = this._queue[position - (execLength - i)];
  47. try {
  48. value[1][i] = cmd.transformReply(value[1][i]);
  49. }
  50. catch (err) {
  51. value[1][i] = err;
  52. }
  53. }
  54. }
  55. this._result[position] = value;
  56. if (--this.replyPending) {
  57. return;
  58. }
  59. if (this.isCluster) {
  60. let retriable = true;
  61. let commonError;
  62. let inTransaction;
  63. for (let i = 0; i < this._result.length; ++i) {
  64. var error = this._result[i][0];
  65. var command = this._queue[i];
  66. if (command.name === "multi") {
  67. inTransaction = true;
  68. }
  69. else if (command.name === "exec") {
  70. inTransaction = false;
  71. }
  72. if (error) {
  73. if (command.name === "exec" &&
  74. error.message ===
  75. "EXECABORT Transaction discarded because of previous errors.") {
  76. continue;
  77. }
  78. if (!commonError) {
  79. commonError = {
  80. name: error.name,
  81. message: error.message
  82. };
  83. }
  84. else if (commonError.name !== error.name ||
  85. commonError.message !== error.message) {
  86. retriable = false;
  87. break;
  88. }
  89. }
  90. else if (!inTransaction) {
  91. var isReadOnly = redis_commands_1.exists(command.name) && redis_commands_1.hasFlag(command.name, "readonly");
  92. if (!isReadOnly) {
  93. retriable = false;
  94. break;
  95. }
  96. }
  97. }
  98. if (commonError && retriable) {
  99. var _this = this;
  100. var errv = commonError.message.split(" ");
  101. var queue = this._queue;
  102. inTransaction = false;
  103. this._queue = [];
  104. for (let i = 0; i < queue.length; ++i) {
  105. if (errv[0] === "ASK" &&
  106. !inTransaction &&
  107. queue[i].name !== "asking" &&
  108. (!queue[i - 1] || queue[i - 1].name !== "asking")) {
  109. var asking = new command_1.default("asking");
  110. asking.ignore = true;
  111. this.sendCommand(asking);
  112. }
  113. queue[i].initPromise();
  114. this.sendCommand(queue[i]);
  115. if (queue[i].name === "multi") {
  116. inTransaction = true;
  117. }
  118. else if (queue[i].name === "exec") {
  119. inTransaction = false;
  120. }
  121. }
  122. let matched = true;
  123. if (typeof this.leftRedirections === "undefined") {
  124. this.leftRedirections = {};
  125. }
  126. const exec = function () {
  127. _this.exec();
  128. };
  129. this.redis.handleError(commonError, this.leftRedirections, {
  130. moved: function (slot, key) {
  131. _this.preferKey = key;
  132. _this.redis.slots[errv[1]] = [key];
  133. _this.redis.refreshSlotsCache();
  134. _this.exec();
  135. },
  136. ask: function (slot, key) {
  137. _this.preferKey = key;
  138. _this.exec();
  139. },
  140. tryagain: exec,
  141. clusterDown: exec,
  142. connectionClosed: exec,
  143. maxRedirections: () => {
  144. matched = false;
  145. },
  146. defaults: () => {
  147. matched = false;
  148. }
  149. });
  150. if (matched) {
  151. return;
  152. }
  153. }
  154. }
  155. let ignoredCount = 0;
  156. for (let i = 0; i < this._queue.length - ignoredCount; ++i) {
  157. if (this._queue[i + ignoredCount].ignore) {
  158. ignoredCount += 1;
  159. }
  160. this._result[i] = this._result[i + ignoredCount];
  161. }
  162. this.resolve(this._result.slice(0, this._result.length - ignoredCount));
  163. };
  164. Pipeline.prototype.sendCommand = function (command) {
  165. const position = this._queue.length;
  166. command.promise
  167. .then(result => {
  168. this.fillResult([null, result], position);
  169. })
  170. .catch(error => {
  171. this.fillResult([error], position);
  172. });
  173. this._queue.push(command);
  174. return this;
  175. };
  176. Pipeline.prototype.addBatch = function (commands) {
  177. let command, commandName, args;
  178. for (let i = 0; i < commands.length; ++i) {
  179. command = commands[i];
  180. commandName = command[0];
  181. args = command.slice(1);
  182. this[commandName].apply(this, args);
  183. }
  184. return this;
  185. };
  186. const multi = Pipeline.prototype.multi;
  187. Pipeline.prototype.multi = function () {
  188. this._transactions += 1;
  189. return multi.apply(this, arguments);
  190. };
  191. const execBuffer = Pipeline.prototype.execBuffer;
  192. const exec = Pipeline.prototype.exec;
  193. Pipeline.prototype.execBuffer = util_1.deprecate(function () {
  194. if (this._transactions > 0) {
  195. this._transactions -= 1;
  196. }
  197. return execBuffer.apply(this, arguments);
  198. }, "Pipeline#execBuffer: Use Pipeline#exec instead");
  199. Pipeline.prototype.exec = function (callback) {
  200. if (this._transactions > 0) {
  201. this._transactions -= 1;
  202. return (this.options.dropBufferSupport ? exec : execBuffer).apply(this, arguments);
  203. }
  204. if (!this.nodeifiedPromise) {
  205. this.nodeifiedPromise = true;
  206. standard_as_callback_1.default(this.promise, callback);
  207. }
  208. if (!this._queue.length) {
  209. this.resolve([]);
  210. }
  211. let pipelineSlot;
  212. if (this.isCluster) {
  213. // List of the first key for each command
  214. const sampleKeys = [];
  215. for (let i = 0; i < this._queue.length; i++) {
  216. var keys = this._queue[i].getKeys();
  217. if (keys.length) {
  218. sampleKeys.push(keys[0]);
  219. }
  220. }
  221. if (sampleKeys.length) {
  222. pipelineSlot = cluster_key_slot_1.generateMulti(sampleKeys);
  223. if (pipelineSlot < 0) {
  224. this.reject(new Error("All keys in the pipeline should belong to the same slot"));
  225. return this.promise;
  226. }
  227. }
  228. else {
  229. // Send the pipeline to a random node
  230. pipelineSlot = (Math.random() * 16384) | 0;
  231. }
  232. }
  233. // Check whether scripts exists
  234. const scripts = [];
  235. for (let i = 0; i < this._queue.length; ++i) {
  236. var item = this._queue[i];
  237. if (this.isCluster && item.isCustomCommand) {
  238. this.reject(new Error("Sending custom commands in pipeline is not supported in Cluster mode."));
  239. return this.promise;
  240. }
  241. if (item.name !== "evalsha") {
  242. continue;
  243. }
  244. const script = this._shaToScript[item.args[0]];
  245. if (!script) {
  246. continue;
  247. }
  248. scripts.push(script);
  249. }
  250. var _this = this;
  251. if (!scripts.length) {
  252. return execPipeline();
  253. }
  254. return this.redis
  255. .script("exists", Array.from(new Set(scripts.map(({ sha }) => sha))))
  256. .then(function (results) {
  257. var pending = [];
  258. for (var i = 0; i < results.length; ++i) {
  259. if (!results[i]) {
  260. pending.push(scripts[i]);
  261. }
  262. }
  263. var Promise = PromiseContainer.get();
  264. return Promise.all(pending.map(function (script) {
  265. return _this.redis.script("load", script.lua);
  266. }));
  267. })
  268. .then(execPipeline);
  269. function execPipeline() {
  270. let data = "";
  271. let writePending = (_this.replyPending = _this._queue.length);
  272. let node;
  273. if (_this.isCluster) {
  274. node = {
  275. slot: pipelineSlot,
  276. redis: _this.redis.connectionPool.nodes.all[_this.preferKey]
  277. };
  278. }
  279. let bufferMode = false;
  280. const stream = {
  281. write: function (writable) {
  282. if (writable instanceof Buffer) {
  283. bufferMode = true;
  284. }
  285. if (bufferMode) {
  286. data = Buffer.concat([
  287. typeof data === "string" ? Buffer.from(data, "utf8") : data,
  288. typeof writable === "string"
  289. ? Buffer.from(writable, "utf8")
  290. : writable
  291. ]);
  292. }
  293. else {
  294. data += writable;
  295. }
  296. if (!--writePending) {
  297. if (_this.isCluster) {
  298. node.redis.stream.write(data);
  299. }
  300. else {
  301. _this.redis.stream.write(data);
  302. }
  303. // Reset writePending for resending
  304. writePending = _this._queue.length;
  305. data = "";
  306. bufferMode = false;
  307. }
  308. }
  309. };
  310. for (let i = 0; i < _this._queue.length; ++i) {
  311. _this.redis.sendCommand(_this._queue[i], stream, node);
  312. }
  313. return _this.promise;
  314. }
  315. };