ioredis 4.28.0 → 4.28.4

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,33 @@
1
+ ## [4.28.4](https://github.com/luin/ioredis/compare/v4.28.3...v4.28.4) (2022-02-02)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * make sure timers is cleared on exit ([#1502](https://github.com/luin/ioredis/issues/1502)) ([cfb04a0](https://github.com/luin/ioredis/commit/cfb04a062b380bad5655b6f97b4259f328f1ee4a))
7
+
8
+ ## [4.28.3](https://github.com/luin/ioredis/compare/v4.28.2...v4.28.3) (2022-01-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * fix exceptions on messages of client side cache ([#1479](https://github.com/luin/ioredis/issues/1479)) ([02adca4](https://github.com/luin/ioredis/commit/02adca4bc1cc50a232703d2b48ea41a18fa82c93))
14
+
15
+ ## [4.28.2](https://github.com/luin/ioredis/compare/v4.28.1...v4.28.2) (2021-12-01)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * add Redis campaign ([#1475](https://github.com/luin/ioredis/issues/1475)) ([3f3d8e9](https://github.com/luin/ioredis/commit/3f3d8e9eb868f4e58bb63926d3b683d9892835f2))
21
+ * fix a memory leak with autopipelining. ([#1470](https://github.com/luin/ioredis/issues/1470)) ([f5d8b73](https://github.com/luin/ioredis/commit/f5d8b73c747a0db5cb36e83e6fe022a19a544bd2))
22
+ * unhandled Promise rejections in pipeline.exec [skip ci] ([#1466](https://github.com/luin/ioredis/issues/1466)) ([e5615da](https://github.com/luin/ioredis/commit/e5615da8786956df08a9b33b6cd4dd31e6eaa759))
23
+
24
+ ## [4.28.1](https://github.com/luin/ioredis/compare/v4.28.0...v4.28.1) (2021-11-23)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * handle possible unhandled promise rejection with autopipelining+cluster ([#1467](https://github.com/luin/ioredis/issues/1467)) ([6ad285a](https://github.com/luin/ioredis/commit/6ad285a59f4a46d5452a799371dfbd69a07ac9f9)), closes [#1466](https://github.com/luin/ioredis/issues/1466)
30
+
1
31
  # [4.28.0](https://github.com/luin/ioredis/compare/v4.27.11...v4.28.0) (2021-10-13)
2
32
 
3
33
 
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  [![ioredis](https://cdn.jsdelivr.net/gh/luin/ioredis@b5e8c74/logo.svg)](https://github.com/luin/ioredis)
2
2
 
3
3
  [![Build Status](https://travis-ci.org/luin/ioredis.svg?branch=master)](https://travis-ci.org/luin/ioredis)
4
+ [![Build Status](https://github.com/luin/ioredis/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/luin/ioredis/actions/workflows/main.yml?query=branch%3Amaster)
4
5
  [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
5
6
  [![Join the chat at https://gitter.im/luin/ioredis](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/luin/ioredis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6
7
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
@@ -42,11 +43,34 @@ used in the world's biggest online commerce company [Alibaba](http://www.alibaba
42
43
  - [Error Handling](#error-handling)
43
44
 
44
45
  <hr>
45
- <a href="http://bit.ly/medis-macos"><img align="right" src="resources/medis.png" alt="Download on the App Store"></a>
46
46
 
47
- ### [AD] Medis: Redis GUI for OS X
47
+ # Sponsors
48
48
 
49
- Looking for a Redis GUI manager for OS X, Windows and Linux? Here's [Medis](http://bit.ly/medis-macos)!
49
+ ### Upstash: Serverless Database for Redis
50
+
51
+ <a href="https://upstash.com/?utm_source=ioredis"><img align="right" width="320" src="resources/upstash.png" alt="Upstash"></a>
52
+
53
+ Upstash is a Serverless Database with Redis/REST API and durable storage. It is the perfect database for your applications thanks to its per request pricing and low latency data.
54
+
55
+ [Start for free in 30 seconds!](https://upstash.com/?utm_source=ioredis)
56
+
57
+ <br clear="both"/>
58
+
59
+ ### Redis Cloud: From the creators of Redis
60
+
61
+ <a href="https://redis.info/ioredis-tryfree"><img align="right" width="320" src="resources/redis-tryfree.png" alt="redis-tryfree"></a>
62
+
63
+ Experience the best Redis. For a limited time, sign up to Redis Enterprise Cloud and use **MATRIX200** to get $200 credits, and a chance to win a Tesla!
64
+
65
+ [Sign Up Now!](https://redis.info/ioredis-tryfree)
66
+
67
+ <br clear="both"/>
68
+
69
+ ### Medis: Redis GUI for macOS
70
+
71
+ <a href="https://getmedis.com/"><img align="right" width="404" src="resources/medis.png" alt="Download on the App Store"></a>
72
+
73
+ Looking for a Redis GUI for macOS, Windows and Linux? Here's [Medis](https://getmedis.com/)!
50
74
 
51
75
  Medis is an open-sourced, beautiful, easy-to-use Redis GUI management application.
52
76
 
@@ -55,14 +79,11 @@ Medis starts with all the basic features you need:
55
79
  - Keys viewing/editing
56
80
  - SSH Tunnel for connecting with remote servers
57
81
  - Terminal for executing custom commands
58
- - JSON/MessagePack format viewing/editing and built-in highlighting/validator
59
82
  - And other awesome features...
60
83
 
61
- [Medis is open sourced on GitHub](https://github.com/luin/medis)
62
-
63
- ### [AD] Kuber: Kubernetes Dashboard for iOS
84
+ [Medis 1 is open sourced on GitHub](https://github.com/luin/medis)
64
85
 
65
- <a href="http://bit.ly/kuber-ios"><img src="resources/kuber.png" alt="Download on the App Store"></a>
86
+ <br clear="both"/>
66
87
 
67
88
  <hr>
68
89
 
@@ -144,7 +165,7 @@ new Redis(
144
165
  );
145
166
  ```
146
167
 
147
- See [API Documentation](API.md#new_Redis) for all available options.
168
+ See [API Documentation](API.md#new-redisport-host-options) for all available options.
148
169
 
149
170
  ## Pub/Sub
150
171
 
@@ -863,6 +884,7 @@ The arguments passed to the constructor are different from the ones you use to c
863
884
  - `sentinels` are a list of sentinels to connect to. The list does not need to enumerate all your sentinel instances, but a few so that if one is down the client will try the next one.
864
885
  - `role` (optional) with a value of `slave` will return a random slave from the Sentinel group.
865
886
  - `preferredSlaves` (optional) can be used to prefer a particular slave or set of slaves based on priority. It accepts a function or array.
887
+ - `enableTLSForSentinelMode` (optional) set to true if connecting to sentinel instances that are encrypted
866
888
 
867
889
  ioredis **guarantees** that the node you connected to is always a master even after a failover. When a failover happens, instead of trying to reconnect to the failed node (which will be demoted to slave when it's available again), ioredis will ask sentinels for the new master node and connect to it. All commands sent during the failover are queued and will be executed when the new connection is established so that none of the commands will be lost.
868
890
 
@@ -77,7 +77,7 @@ class DataHandler {
77
77
  case "message":
78
78
  if (this.redis.listeners("message").length > 0) {
79
79
  // Check if there're listeners to avoid unnecessary `toString()`.
80
- this.redis.emit("message", reply[1].toString(), reply[2].toString());
80
+ this.redis.emit("message", reply[1].toString(), reply[2] ? reply[2].toString() : '');
81
81
  }
82
82
  this.redis.emit("messageBuffer", reply[1], reply[2]);
83
83
  break;
@@ -42,6 +42,10 @@ function executeAutoPipeline(client, slotKey) {
42
42
  const pipeline = client._autoPipelines.get(slotKey);
43
43
  client._autoPipelines.delete(slotKey);
44
44
  const callbacks = pipeline[exports.kCallbacks];
45
+ // Stop keeping a reference to callbacks immediately after the callbacks stop being used.
46
+ // This allows the GC to reclaim objects referenced by callbacks, especially with 16384 slots
47
+ // in Redis.Cluster
48
+ pipeline[exports.kCallbacks] = null;
45
49
  // Perform the call
46
50
  pipeline.exec(function (err, results) {
47
51
  client._runningAutoPipelines.delete(slotKey);
@@ -102,15 +106,15 @@ function executeWithAutoPipelining(client, functionName, commandName, args, call
102
106
  if (client.isCluster && !client.slots.length) {
103
107
  if (client.status === "wait")
104
108
  client.connect().catch(lodash_1.noop);
105
- return new CustomPromise(function (resolve, reject) {
109
+ return standard_as_callback_1.default(new CustomPromise(function (resolve, reject) {
106
110
  client.delayUntilReady((err) => {
107
111
  if (err) {
108
112
  reject(err);
109
113
  return;
110
114
  }
111
- executeWithAutoPipelining(client, functionName, commandName, args, callback).then(resolve, reject);
115
+ executeWithAutoPipelining(client, functionName, commandName, args, null).then(resolve, reject);
112
116
  });
113
- });
117
+ }), callback);
114
118
  }
115
119
  // If we have slot information, we can improve routing by grouping slots served by the same subset of nodes
116
120
  // Note that the first value in args may be a (possibly empty) array.
@@ -139,7 +143,7 @@ function executeWithAutoPipelining(client, functionName, commandName, args, call
139
143
  */
140
144
  setImmediate(executeAutoPipeline, client, slotKey);
141
145
  }
142
- // Create the promise which will execute the
146
+ // Create the promise which will execute the command in the pipeline.
143
147
  const autoPipelinePromise = new CustomPromise(function (resolve, reject) {
144
148
  pipeline[exports.kCallbacks].push(function (err, value) {
145
149
  if (err) {
@@ -100,6 +100,12 @@ class Cluster extends events_1.EventEmitter {
100
100
  this.slotsTimer = null;
101
101
  }
102
102
  }
103
+ clearAddedScriptHashesCleanInterval() {
104
+ if (this._addedScriptHashesCleanInterval) {
105
+ clearInterval(this._addedScriptHashesCleanInterval);
106
+ this._addedScriptHashesCleanInterval = null;
107
+ }
108
+ }
103
109
  resetNodesRefreshInterval() {
104
110
  if (this.slotsTimer) {
105
111
  return;
@@ -130,7 +136,7 @@ class Cluster extends events_1.EventEmitter {
130
136
  return;
131
137
  }
132
138
  // Make sure only one timer is active at a time
133
- clearInterval(this._addedScriptHashesCleanInterval);
139
+ this.clearAddedScriptHashesCleanInterval();
134
140
  // Start the script cache cleaning
135
141
  this._addedScriptHashesCleanInterval = setInterval(() => {
136
142
  this._addedScriptHashes = {};
@@ -215,6 +221,7 @@ class Cluster extends events_1.EventEmitter {
215
221
  if (reason) {
216
222
  debug("closed because %s", reason);
217
223
  }
224
+ this.clearAddedScriptHashesCleanInterval();
218
225
  let retryDelay;
219
226
  if (!this.manuallyClosing &&
220
227
  typeof this.options.clusterRetryStrategy === "function") {
@@ -244,8 +251,7 @@ class Cluster extends events_1.EventEmitter {
244
251
  disconnect(reconnect = false) {
245
252
  const status = this.status;
246
253
  this.setStatus("disconnecting");
247
- clearInterval(this._addedScriptHashesCleanInterval);
248
- this._addedScriptHashesCleanInterval = null;
254
+ this.clearAddedScriptHashesCleanInterval();
249
255
  if (!reconnect) {
250
256
  this.manuallyClosing = true;
251
257
  }
@@ -274,8 +280,7 @@ class Cluster extends events_1.EventEmitter {
274
280
  quit(callback) {
275
281
  const status = this.status;
276
282
  this.setStatus("disconnecting");
277
- clearInterval(this._addedScriptHashesCleanInterval);
278
- this._addedScriptHashesCleanInterval = null;
283
+ this.clearAddedScriptHashesCleanInterval();
279
284
  this.manuallyClosing = true;
280
285
  if (this.reconnectTimeout) {
281
286
  clearTimeout(this.reconnectTimeout);
@@ -495,7 +500,8 @@ class Cluster extends events_1.EventEmitter {
495
500
  else {
496
501
  _this.slots[slot] = [key];
497
502
  }
498
- _this._groupsBySlot[slot] = _this._groupsIds[_this.slots[slot].join(';')];
503
+ _this._groupsBySlot[slot] =
504
+ _this._groupsIds[_this.slots[slot].join(";")];
499
505
  _this.connectionPool.findOrCreate(_this.natMapper(key));
500
506
  tryConnection();
501
507
  debug("refreshing slot caches... (triggered by MOVED error)");
@@ -702,7 +708,7 @@ class Cluster extends events_1.EventEmitter {
702
708
  this._groupsIds = Object.create(null);
703
709
  let j = 0;
704
710
  for (let i = 0; i < 16384; i++) {
705
- const target = (this.slots[i] || []).join(';');
711
+ const target = (this.slots[i] || []).join(";");
706
712
  if (!target.length) {
707
713
  this._groupsBySlot[i] = undefined;
708
714
  continue;
package/built/pipeline.js CHANGED
@@ -213,6 +213,12 @@ Pipeline.prototype.execBuffer = util_1.deprecate(function () {
213
213
  }
214
214
  return execBuffer.apply(this, arguments);
215
215
  }, "Pipeline#execBuffer: Use Pipeline#exec instead");
216
+ // NOTE: To avoid an unhandled promise rejection, this will unconditionally always return this.promise,
217
+ // which always has the rejection handled by standard-as-callback
218
+ // adding the provided rejection callback.
219
+ //
220
+ // If a different promise instance were returned, that promise would cause its own unhandled promise rejection
221
+ // errors, even if that promise unconditionally resolved to **the resolved value of** this.promise.
216
222
  Pipeline.prototype.exec = function (callback) {
217
223
  // Wait for the cluster to be connected, since we need nodes information before continuing
218
224
  if (this.isCluster && !this.redis.slots.length) {
@@ -286,16 +292,18 @@ Pipeline.prototype.exec = function (callback) {
286
292
  }
287
293
  // In cluster mode, always load scripts before running the pipeline
288
294
  if (this.isCluster) {
289
- return pMap(scripts, (script) => _this.redis.script("load", script.lua), {
295
+ pMap(scripts, (script) => _this.redis.script("load", script.lua), {
290
296
  concurrency: 10,
291
- }).then(function () {
297
+ })
298
+ .then(function () {
292
299
  for (let i = 0; i < scripts.length; i++) {
293
300
  _this.redis._addedScriptHashes[scripts[i].sha] = true;
294
301
  }
295
- return execPipeline();
296
- });
302
+ })
303
+ .then(execPipeline, this.reject);
304
+ return this.promise;
297
305
  }
298
- return this.redis
306
+ this.redis
299
307
  .script("exists", scripts.map(({ sha }) => sha))
300
308
  .then(function (results) {
301
309
  const pending = [];
@@ -313,8 +321,9 @@ Pipeline.prototype.exec = function (callback) {
313
321
  for (let i = 0; i < scripts.length; i++) {
314
322
  _this.redis._addedScriptHashes[scripts[i].sha] = true;
315
323
  }
316
- return execPipeline();
317
- });
324
+ })
325
+ .then(execPipeline, this.reject);
326
+ return this.promise;
318
327
  function execPipeline() {
319
328
  let data = "";
320
329
  let buffers;
@@ -144,6 +144,7 @@ function closeHandler(self) {
144
144
  if (self.offlineQueue.length) {
145
145
  abortTransactionFragments(self.offlineQueue);
146
146
  }
147
+ self.clearAddedScriptHashesCleanInterval();
147
148
  if (self.manuallyClosing) {
148
149
  self.manuallyClosing = false;
149
150
  debug("skip reconnecting since the connection is manually closed.");
@@ -243,6 +243,12 @@ Redis.prototype.setStatus = function (status, arg) {
243
243
  this.status = status;
244
244
  process.nextTick(this.emit.bind(this, status, arg));
245
245
  };
246
+ Redis.prototype.clearAddedScriptHashesCleanInterval = function () {
247
+ if (this._addedScriptHashesCleanInterval) {
248
+ clearInterval(this._addedScriptHashesCleanInterval);
249
+ this._addedScriptHashesCleanInterval = null;
250
+ }
251
+ };
246
252
  /**
247
253
  * Create a connection to Redis.
248
254
  * This method will be invoked automatically when creating a new Redis instance
@@ -264,7 +270,7 @@ Redis.prototype.connect = function (callback) {
264
270
  return;
265
271
  }
266
272
  // Make sure only one timer is active at a time
267
- clearInterval(this._addedScriptHashesCleanInterval);
273
+ this.clearAddedScriptHashesCleanInterval();
268
274
  // Start the script cache cleaning
269
275
  this._addedScriptHashesCleanInterval = setInterval(() => {
270
276
  this._addedScriptHashes = {};
@@ -372,8 +378,7 @@ Redis.prototype.connect = function (callback) {
372
378
  * @public
373
379
  */
374
380
  Redis.prototype.disconnect = function (reconnect) {
375
- clearInterval(this._addedScriptHashesCleanInterval);
376
- this._addedScriptHashesCleanInterval = null;
381
+ this.clearAddedScriptHashesCleanInterval();
377
382
  if (!reconnect) {
378
383
  this.manuallyClosing = true;
379
384
  }
@@ -637,8 +642,7 @@ Redis.prototype.sendCommand = function (command, stream) {
637
642
  command.setTimeout(this.options.commandTimeout);
638
643
  }
639
644
  if (command.name === "quit") {
640
- clearInterval(this._addedScriptHashesCleanInterval);
641
- this._addedScriptHashesCleanInterval = null;
645
+ this.clearAddedScriptHashesCleanInterval();
642
646
  }
643
647
  let writable = this.status === "ready" ||
644
648
  (!stream &&
@@ -97,7 +97,7 @@ function wrapMultiResult(arr) {
97
97
  }
98
98
  exports.wrapMultiResult = wrapMultiResult;
99
99
  /**
100
- * Detect the argument is a int
100
+ * Detect if the argument is a int
101
101
  *
102
102
  * @param {string} value
103
103
  * @return {boolean} Whether the value is a int
@@ -174,7 +174,7 @@ exports.timeout = timeout;
174
174
  */
175
175
  function convertObjectToArray(obj) {
176
176
  const result = [];
177
- const keys = Object.keys(obj);
177
+ const keys = Object.keys(obj); // Object.entries requires node 7+
178
178
  for (let i = 0, l = keys.length; i < l; i++) {
179
179
  result.push(keys[i], obj[keys[i]]);
180
180
  }
@@ -188,7 +188,7 @@ exports.convertObjectToArray = convertObjectToArray;
188
188
  * @return {array}
189
189
  * @example
190
190
  * ```js
191
- * > convertObjectToArray(new Map([[1, '2']]))
191
+ * > convertMapToArray(new Map([[1, '2']]))
192
192
  * [1, '2']
193
193
  * ```
194
194
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ioredis",
3
- "version": "4.28.0",
3
+ "version": "4.28.4",
4
4
  "description": "A robust, performance-focused and full-featured Redis client for Node.js.",
5
5
  "main": "built/index.js",
6
6
  "files": [