ioredis 4.27.5 → 4.27.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Changelog.md CHANGED
@@ -1,3 +1,32 @@
1
+ ## [4.27.9](https://github.com/luin/ioredis/compare/v4.27.8...v4.27.9) (2021-08-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Fix undefined property warning in executeAutoPipeline ([#1425](https://github.com/luin/ioredis/issues/1425)) ([f898672](https://github.com/luin/ioredis/commit/f898672a29753774eeb6e166c28ed6f548533517))
7
+ * improve proto checking for hgetall [skip ci] ([#1418](https://github.com/luin/ioredis/issues/1418)) ([cba83cb](https://github.com/luin/ioredis/commit/cba83cba2dba25e59ad87c85d740f15f78e45e14))
8
+
9
+ ## [4.27.8](https://github.com/luin/ioredis/compare/v4.27.7...v4.27.8) (2021-08-18)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * handle malicious keys for hgetall ([#1416](https://github.com/luin/ioredis/issues/1416)) ([7d73b9d](https://github.com/luin/ioredis/commit/7d73b9d07b52ec077f235292aa15c7aca203bba9)), closes [#1267](https://github.com/luin/ioredis/issues/1267)
15
+
16
+ ## [4.27.7](https://github.com/luin/ioredis/compare/v4.27.6...v4.27.7) (2021-08-01)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **cluster:** fix autopipeline with keyPrefix or arg array ([#1391](https://github.com/luin/ioredis/issues/1391)) ([d7477aa](https://github.com/luin/ioredis/commit/d7477aa5853388b51037210542372131919ddfb2)), closes [#1264](https://github.com/luin/ioredis/issues/1264) [#1248](https://github.com/luin/ioredis/issues/1248) [#1392](https://github.com/luin/ioredis/issues/1392)
22
+
23
+ ## [4.27.6](https://github.com/luin/ioredis/compare/v4.27.5...v4.27.6) (2021-06-13)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * fixed autopipeline performances. ([#1226](https://github.com/luin/ioredis/issues/1226)) ([42f1ee1](https://github.com/luin/ioredis/commit/42f1ee107174366a79ff94bec8a7a1ac353e035c))
29
+
1
30
  ## [4.27.5](https://github.com/luin/ioredis/compare/v4.27.4...v4.27.5) (2021-06-05)
2
31
 
3
32
 
package/README.md CHANGED
@@ -755,7 +755,7 @@ const redis = new Redis({
755
755
 
756
756
  This feature is useful when using Amazon ElastiCache instances with Auto-failover disabled. On these instances, test your `reconnectOnError` handler by manually promoting the replica node to the primary role using the AWS console. The following writes fail with the error `READONLY`. Using `reconnectOnError`, we can force the connection to reconnect on this error in order to connect to the new master. Furthermore, if the `reconnectOnError` returns `2`, ioredis will resend the failed command after reconnecting.
757
757
 
758
- On ElastiCache insances with Auto-failover enabled, `reconnectOnError` does not execute. Instead of returning a Redis error, AWS closes all connections to the master endpoint until the new primary node is ready. ioredis reconnects via `retryStrategy` instead of `reconnectOnError` after about a minute. On ElastiCache insances with Auto-failover enabled, test failover events with the `Failover primary` option in the AWS console.
758
+ On ElastiCache instances with Auto-failover enabled, `reconnectOnError` does not execute. Instead of returning a Redis error, AWS closes all connections to the master endpoint until the new primary node is ready. ioredis reconnects via `retryStrategy` instead of `reconnectOnError` after about a minute. On ElastiCache instances with Auto-failover enabled, test failover events with the `Failover primary` option in the AWS console.
759
759
 
760
760
  ## Connection Events
761
761
 
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const PromiseContainer = require("./promiseContainer");
4
+ const lodash_1 = require("./utils/lodash");
4
5
  const calculateSlot = require("cluster-key-slot");
5
6
  const standard_as_callback_1 = require("standard-as-callback");
6
7
  exports.kExec = Symbol("exec");
@@ -18,13 +19,6 @@ exports.notAllowedAutoPipelineCommands = [
18
19
  "unsubscribe",
19
20
  "unpsubscribe",
20
21
  ];
21
- function findAutoPipeline(client, _commandName, ...args) {
22
- if (!client.isCluster) {
23
- return "main";
24
- }
25
- // We have slot information, we can improve routing by grouping slots served by the same subset of nodes
26
- return client.slots[calculateSlot(args[0])].join(",");
27
- }
28
22
  function executeAutoPipeline(client, slotKey) {
29
23
  /*
30
24
  If a pipeline is already executing, keep queueing up commands
@@ -33,6 +27,16 @@ function executeAutoPipeline(client, slotKey) {
33
27
  if (client._runningAutoPipelines.has(slotKey)) {
34
28
  return;
35
29
  }
30
+ if (!client._autoPipelines.has(slotKey)) {
31
+ /*
32
+ Rare edge case. Somehow, something has deleted this running autopipeline in an immediate
33
+ call to executeAutoPipeline.
34
+
35
+ Maybe the callback in the pipeline.exec is sometimes called in the same tick,
36
+ e.g. if redis is disconnected?
37
+ */
38
+ return;
39
+ }
36
40
  client._runningAutoPipelines.add(slotKey);
37
41
  // Get the pipeline and immediately delete it so that new commands are queued on a new pipeline
38
42
  const pipeline = client._autoPipelines.get(slotKey);
@@ -69,6 +73,29 @@ function shouldUseAutoPipelining(client, functionName, commandName) {
69
73
  !client.options.autoPipeliningIgnoredCommands.includes(commandName));
70
74
  }
71
75
  exports.shouldUseAutoPipelining = shouldUseAutoPipelining;
76
+ /**
77
+ * @private
78
+ */
79
+ function getFirstValueInFlattenedArray(args) {
80
+ for (let i = 0; i < args.length; i++) {
81
+ const arg = args[i];
82
+ if (typeof arg === "string") {
83
+ return arg;
84
+ }
85
+ else if (Array.isArray(arg) || lodash_1.isArguments(arg)) {
86
+ if (arg.length === 0) {
87
+ continue;
88
+ }
89
+ return arg[0];
90
+ }
91
+ const flattened = lodash_1.flatten([arg]);
92
+ if (flattened.length > 0) {
93
+ return flattened[0];
94
+ }
95
+ }
96
+ return undefined;
97
+ }
98
+ exports.getFirstValueInFlattenedArray = getFirstValueInFlattenedArray;
72
99
  function executeWithAutoPipelining(client, functionName, commandName, args, callback) {
73
100
  const CustomPromise = PromiseContainer.get();
74
101
  // On cluster mode let's wait for slots to be available
@@ -83,7 +110,13 @@ function executeWithAutoPipelining(client, functionName, commandName, args, call
83
110
  });
84
111
  });
85
112
  }
86
- const slotKey = findAutoPipeline(client, commandName, ...args);
113
+ // If we have slot information, we can improve routing by grouping slots served by the same subset of nodes
114
+ // Note that the first value in args may be a (possibly empty) array.
115
+ // ioredis will only flatten one level of the array, in the Command constructor.
116
+ const prefix = client.options.keyPrefix || "";
117
+ const slotKey = client.isCluster
118
+ ? client.slots[calculateSlot(`${prefix}${getFirstValueInFlattenedArray(args)}`)].join(",")
119
+ : "main";
87
120
  if (!client._autoPipelines.has(slotKey)) {
88
121
  const pipeline = client.pipeline();
89
122
  pipeline[exports.kExec] = false;
@@ -42,6 +42,8 @@ class Cluster extends events_1.EventEmitter {
42
42
  this.isRefreshing = false;
43
43
  this.isCluster = true;
44
44
  this._autoPipelines = new Map();
45
+ this._groupsIds = {};
46
+ this._groupsBySlot = Array(16384);
45
47
  this._runningAutoPipelines = new Set();
46
48
  this._readyDelayedCallbacks = [];
47
49
  this._addedScriptHashes = {};
@@ -127,7 +129,9 @@ class Cluster extends events_1.EventEmitter {
127
129
  reject(new Error("Redis is already connecting/connected"));
128
130
  return;
129
131
  }
132
+ // Make sure only one timer is active at a time
130
133
  clearInterval(this._addedScriptHashesCleanInterval);
134
+ // Start the script cache cleaning
131
135
  this._addedScriptHashesCleanInterval = setInterval(() => {
132
136
  this._addedScriptHashes = {};
133
137
  }, this.options.maxScriptsCachingTime);
@@ -491,6 +495,7 @@ class Cluster extends events_1.EventEmitter {
491
495
  else {
492
496
  _this.slots[slot] = [key];
493
497
  }
498
+ _this._groupsBySlot[slot] = _this._groupsIds[_this.slots[slot].join(';')];
494
499
  _this.connectionPool.findOrCreate(_this.natMapper(key));
495
500
  tryConnection();
496
501
  debug("refreshing slot caches... (triggered by MOVED error)");
@@ -693,6 +698,20 @@ class Cluster extends events_1.EventEmitter {
693
698
  this.slots[slot] = keys;
694
699
  }
695
700
  }
701
+ // Assign to each node keys a numeric value to make autopipeline comparison faster.
702
+ this._groupsIds = Object.create(null);
703
+ let j = 0;
704
+ for (let i = 0; i < 16384; i++) {
705
+ const target = (this.slots[i] || []).join(';');
706
+ if (!target.length) {
707
+ this._groupsBySlot[i] = undefined;
708
+ continue;
709
+ }
710
+ if (!this._groupsIds[target]) {
711
+ this._groupsIds[target] = ++j;
712
+ }
713
+ this._groupsBySlot[i] = this._groupsIds[target];
714
+ }
696
715
  this.connectionPool.reset(nodes);
697
716
  callback();
698
717
  }, this.options.slotsRefreshTimeout));
package/built/command.js CHANGED
@@ -313,7 +313,21 @@ Command.setReplyTransformer("hgetall", function (result) {
313
313
  if (Array.isArray(result)) {
314
314
  const obj = {};
315
315
  for (let i = 0; i < result.length; i += 2) {
316
- obj[result[i]] = result[i + 1];
316
+ const key = result[i];
317
+ const value = result[i + 1];
318
+ if (key in obj) {
319
+ // can only be truthy if the property is special somehow, like '__proto__' or 'constructor'
320
+ // https://github.com/luin/ioredis/issues/1267
321
+ Object.defineProperty(obj, key, {
322
+ value,
323
+ configurable: true,
324
+ enumerable: true,
325
+ writable: true,
326
+ });
327
+ }
328
+ else {
329
+ obj[key] = value;
330
+ }
317
331
  }
318
332
  return obj;
319
333
  }
package/built/pipeline.js CHANGED
@@ -15,10 +15,9 @@ const commander_1 = require("./commander");
15
15
  */
16
16
  function generateMultiWithNodes(redis, keys) {
17
17
  const slot = calculateSlot(keys[0]);
18
- const target = redis.slots[slot].join(",");
18
+ const target = redis._groupsBySlot[slot];
19
19
  for (let i = 1; i < keys.length; i++) {
20
- const currentTarget = redis.slots[calculateSlot(keys[i])].join(",");
21
- if (currentTarget !== target) {
20
+ if (redis._groupsBySlot[calculateSlot(keys[i])] !== target) {
22
21
  return -1;
23
22
  }
24
23
  }
@@ -141,6 +140,7 @@ Pipeline.prototype.fillResult = function (value, position) {
141
140
  moved: function (slot, key) {
142
141
  _this.preferKey = key;
143
142
  _this.redis.slots[errv[1]] = [key];
143
+ _this.redis._groupsBySlot[errv[1]] = _this.redis._groupsIds[_this.redis.slots[errv[1]].join(";")];
144
144
  _this.redis.refreshSlotsCache();
145
145
  _this.exec();
146
146
  },
@@ -262,7 +262,9 @@ Redis.prototype.connect = function (callback) {
262
262
  reject(new Error("Redis is already connecting/connected"));
263
263
  return;
264
264
  }
265
+ // Make sure only one timer is active at a time
265
266
  clearInterval(this._addedScriptHashesCleanInterval);
267
+ // Start the script cache cleaning
266
268
  this._addedScriptHashesCleanInterval = setInterval(() => {
267
269
  this._addedScriptHashes = {};
268
270
  }, this.options.maxScriptsCachingTime);
@@ -4,5 +4,7 @@ const defaults = require("lodash.defaults");
4
4
  exports.defaults = defaults;
5
5
  const flatten = require("lodash.flatten");
6
6
  exports.flatten = flatten;
7
+ const isArguments = require("lodash.isarguments");
8
+ exports.isArguments = isArguments;
7
9
  function noop() { }
8
10
  exports.noop = noop;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ioredis",
3
- "version": "4.27.5",
3
+ "version": "4.27.9",
4
4
  "description": "A robust, performance-focused and full-featured Redis client for Node.js.",
5
5
  "main": "built/index.js",
6
6
  "files": [
@@ -38,6 +38,7 @@
38
38
  "denque": "^1.1.0",
39
39
  "lodash.defaults": "^4.2.0",
40
40
  "lodash.flatten": "^4.4.0",
41
+ "lodash.isarguments": "^3.1.0",
41
42
  "p-map": "^2.1.0",
42
43
  "redis-commands": "1.7.0",
43
44
  "redis-errors": "^1.2.0",
@@ -53,6 +54,7 @@
53
54
  "@types/debug": "^4.1.5",
54
55
  "@types/lodash.defaults": "^4.2.6",
55
56
  "@types/lodash.flatten": "^4.4.6",
57
+ "@types/lodash.isarguments": "^3.1.6",
56
58
  "@types/mocha": "^7.0.2",
57
59
  "@types/node": "^13.11.0",
58
60
  "@types/redis-errors": "1.2.0",