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 +30 -0
- package/README.md +31 -9
- package/built/DataHandler.js +1 -1
- package/built/autoPipelining.js +8 -4
- package/built/cluster/index.js +13 -7
- package/built/pipeline.js +16 -7
- package/built/redis/event_handler.js +1 -0
- package/built/redis/index.js +9 -5
- package/built/utils/index.js +3 -3
- package/package.json +1 -1
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
|
[](https://github.com/luin/ioredis)
|
|
2
2
|
|
|
3
3
|
[](https://travis-ci.org/luin/ioredis)
|
|
4
|
+
[](https://github.com/luin/ioredis/actions/workflows/main.yml?query=branch%3Amaster)
|
|
4
5
|
[](https://github.com/prettier/prettier)
|
|
5
6
|
[](https://gitter.im/luin/ioredis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
6
7
|
[](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
|
-
|
|
47
|
+
# Sponsors
|
|
48
48
|
|
|
49
|
-
|
|
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
|
-
<
|
|
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#
|
|
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
|
|
package/built/DataHandler.js
CHANGED
|
@@ -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;
|
package/built/autoPipelining.js
CHANGED
|
@@ -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,
|
|
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) {
|
package/built/cluster/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
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
|
-
|
|
295
|
+
pMap(scripts, (script) => _this.redis.script("load", script.lua), {
|
|
290
296
|
concurrency: 10,
|
|
291
|
-
})
|
|
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
|
-
|
|
296
|
-
|
|
302
|
+
})
|
|
303
|
+
.then(execPipeline, this.reject);
|
|
304
|
+
return this.promise;
|
|
297
305
|
}
|
|
298
|
-
|
|
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
|
-
|
|
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.");
|
package/built/redis/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
641
|
-
this._addedScriptHashesCleanInterval = null;
|
|
645
|
+
this.clearAddedScriptHashesCleanInterval();
|
|
642
646
|
}
|
|
643
647
|
let writable = this.status === "ready" ||
|
|
644
648
|
(!stream &&
|
package/built/utils/index.js
CHANGED
|
@@ -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
|
-
* >
|
|
191
|
+
* > convertMapToArray(new Map([[1, '2']]))
|
|
192
192
|
* [1, '2']
|
|
193
193
|
* ```
|
|
194
194
|
*/
|