Skip to content

Commit

Permalink
Allow override Redis client module
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Grondin committed Jul 16, 2019
1 parent 37d397a commit c7575a0
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 81 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -805,9 +805,21 @@ const limiter = new Bottleneck({
| `clientOptions` | `{}` | This object is passed directly to the redis client library you've selected. |
| `clusterNodes` | `null` | **ioredis only.** When `clusterNodes` is not null, the client will be instantiated by calling `new Redis.Cluster(clusterNodes, clientOptions)` instead of `new Redis(clientOptions)`. |
| `timeout` | `null` (no TTL) | The Redis TTL in milliseconds ([TTL](https://redis.io/commands/ttl)) for the keys created by the limiter. When `timeout` is set, the limiter's state will be automatically removed from Redis after `timeout` milliseconds of inactivity. |
| `Redis` | `null` | Overrides the import/require of the redis/ioredis library. You shouldn't need to set this option unless your application is failing to start due to a failure to require/import the client library. |

**Note: When using Groups**, the `timeout` option has a default of `300000` milliseconds and the generated limiters automatically receive an `id` with the pattern `${group.id}-${KEY}`.

**Note:** If you are seeing a runtime error due to the `require()` function not being able to load `redis`/`ioredis`, then directly pass the module as the `Redis` option. Example:
```js
import Redis from 'ioredis'

const limiter = new Bottleneck({
clientOptions: { host: '12.34.56.78', port: 6379 },
Redis
})
```
Unfortunately, this is a side effect of having to disable inlining, which is necessary to make Bottleneck easy to use in the browser.

### Important considerations when Clustering

The first limiter connecting to Redis will store its [constructor options](#constructor) on Redis and all subsequent limiters will be using those settings. You can alter the constructor options used by all the connected limiters by calling `updateSettings()`. The `clearDatastore` option instructs a new limiter to wipe any previous Bottleneck data (for that `id`), including previously stored settings.
Expand Down
29 changes: 18 additions & 11 deletions es5.js
Original file line number Diff line number Diff line change
Expand Up @@ -2547,19 +2547,20 @@

_classCallCheck(this, RedisConnection);

var Redis;
Redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module

parser$3.load(options, this.defaults, this);

if (this.Redis == null) {
this.Redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module. To override this behavior: pass the redis module to Bottleneck as the 'Redis' option.
}

if (this.Events == null) {
this.Events = new Events$2(this);
}

this.terminated = false;

if (this.client == null) {
this.client = Redis.createClient(this.clientOptions);
this.client = this.Redis.createClient(this.clientOptions);
}

this.subscriber = this.client.duplicate();
Expand Down Expand Up @@ -2776,6 +2777,7 @@
}();
RedisConnection.prototype.datastore = "redis";
RedisConnection.prototype.defaults = {
Redis: null,
clientOptions: {},
client: null,
Promise: Promise,
Expand All @@ -2802,25 +2804,26 @@

_classCallCheck(this, IORedisConnection);

var Redis;
Redis = eval("require")("ioredis"); // Obfuscated or else Webpack/Angular will try to inline the optional ioredis module

parser$4.load(options, this.defaults, this);

if (this.Redis == null) {
this.Redis = eval("require")("ioredis"); // Obfuscated or else Webpack/Angular will try to inline the optional ioredis module. To override this behavior: pass the ioredis module to Bottleneck as the 'Redis' option.
}

if (this.Events == null) {
this.Events = new Events$3(this);
}

this.terminated = false;

if (this.clusterNodes != null) {
this.client = new Redis.Cluster(this.clusterNodes, this.clientOptions);
this.subscriber = new Redis.Cluster(this.clusterNodes, this.clientOptions);
this.client = new this.Redis.Cluster(this.clusterNodes, this.clientOptions);
this.subscriber = new this.Redis.Cluster(this.clusterNodes, this.clientOptions);
} else if (this.client != null && this.client.duplicate == null) {
this.subscriber = new Redis.Cluster(this.client.startupNodes, this.client.options);
this.subscriber = new this.Redis.Cluster(this.client.startupNodes, this.client.options);
} else {
if (this.client == null) {
this.client = new Redis(this.clientOptions);
this.client = new this.Redis(this.clientOptions);
}

this.subscriber = this.client.duplicate();
Expand Down Expand Up @@ -3008,6 +3011,7 @@
}();
IORedisConnection.prototype.datastore = "ioredis";
IORedisConnection.prototype.defaults = {
Redis: null,
clientOptions: {},
clusterNodes: null,
client: null,
Expand Down Expand Up @@ -3044,10 +3048,12 @@

if (this.connection == null) {
this.connection = this.instance.datastore === "redis" ? new RedisConnection$1({
Redis: this.Redis,
clientOptions: this.clientOptions,
Promise: this.Promise,
Events: this.instance.Events
}) : this.instance.datastore === "ioredis" ? new IORedisConnection$1({
Redis: this.Redis,
clientOptions: this.clientOptions,
clusterNodes: this.clusterNodes,
Promise: this.Promise,
Expand Down Expand Up @@ -5023,6 +5029,7 @@
timeout: null,
heartbeatInterval: 5000,
clientTimeout: 10000,
Redis: null,
clientOptions: {},
clusterNodes: null,
clearDatastore: false,
Expand Down
1 change: 1 addition & 0 deletions lib/Bottleneck.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ Bottleneck = function () {
timeout: null,
heartbeatInterval: 5000,
clientTimeout: 10000,
Redis: null,
clientOptions: {},
clusterNodes: null,
clearDatastore: false,
Expand Down
16 changes: 9 additions & 7 deletions lib/IORedisConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,26 @@ Scripts = require("./Scripts");
IORedisConnection = function () {
class IORedisConnection {
constructor(options = {}) {
var Redis;
Redis = eval("require")("ioredis"); // Obfuscated or else Webpack/Angular will try to inline the optional ioredis module

parser.load(options, this.defaults, this);

if (this.Redis == null) {
this.Redis = eval("require")("ioredis"); // Obfuscated or else Webpack/Angular will try to inline the optional ioredis module. To override this behavior: pass the ioredis module to Bottleneck as the 'Redis' option.
}

if (this.Events == null) {
this.Events = new Events(this);
}

this.terminated = false;

if (this.clusterNodes != null) {
this.client = new Redis.Cluster(this.clusterNodes, this.clientOptions);
this.subscriber = new Redis.Cluster(this.clusterNodes, this.clientOptions);
this.client = new this.Redis.Cluster(this.clusterNodes, this.clientOptions);
this.subscriber = new this.Redis.Cluster(this.clusterNodes, this.clientOptions);
} else if (this.client != null && this.client.duplicate == null) {
this.subscriber = new Redis.Cluster(this.client.startupNodes, this.client.options);
this.subscriber = new this.Redis.Cluster(this.client.startupNodes, this.client.options);
} else {
if (this.client == null) {
this.client = new Redis(this.clientOptions);
this.client = new this.Redis(this.clientOptions);
}

this.subscriber = this.client.duplicate();
Expand Down Expand Up @@ -172,6 +173,7 @@ IORedisConnection = function () {
;
IORedisConnection.prototype.datastore = "ioredis";
IORedisConnection.prototype.defaults = {
Redis: null,
clientOptions: {},
clusterNodes: null,
client: null,
Expand Down
10 changes: 6 additions & 4 deletions lib/RedisConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ Scripts = require("./Scripts");
RedisConnection = function () {
class RedisConnection {
constructor(options = {}) {
var Redis;
Redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module

parser.load(options, this.defaults, this);

if (this.Redis == null) {
this.Redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module. To override this behavior: pass the redis module to Bottleneck as the 'Redis' option.
}

if (this.Events == null) {
this.Events = new Events(this);
}

this.terminated = false;

if (this.client == null) {
this.client = Redis.createClient(this.clientOptions);
this.client = this.Redis.createClient(this.clientOptions);
}

this.subscriber = this.client.duplicate();
Expand Down Expand Up @@ -180,6 +181,7 @@ RedisConnection = function () {
;
RedisConnection.prototype.datastore = "redis";
RedisConnection.prototype.defaults = {
Redis: null,
clientOptions: {},
client: null,
Promise: Promise,
Expand Down
2 changes: 2 additions & 0 deletions lib/RedisDatastore.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ RedisDatastore = class RedisDatastore {

if (this.connection == null) {
this.connection = this.instance.datastore === "redis" ? new RedisConnection({
Redis: this.Redis,
clientOptions: this.clientOptions,
Promise: this.Promise,
Events: this.instance.Events
}) : this.instance.datastore === "ioredis" ? new IORedisConnection({
Redis: this.Redis,
clientOptions: this.clientOptions,
clusterNodes: this.clusterNodes,
Promise: this.Promise,
Expand Down
1 change: 1 addition & 0 deletions light.js
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,7 @@
timeout: null,
heartbeatInterval: 5000,
clientTimeout: 10000,
Redis: null,
clientOptions: {},
clusterNodes: null,
clearDatastore: false,
Expand Down
1 change: 1 addition & 0 deletions src/Bottleneck.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Bottleneck
timeout: null
heartbeatInterval: 5000
clientTimeout: 10000
Redis: null
clientOptions: {}
clusterNodes: null
clearDatastore: false
Expand Down
11 changes: 6 additions & 5 deletions src/IORedisConnection.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@ Scripts = require "./Scripts"
class IORedisConnection
datastore: "ioredis"
defaults:
Redis: null
clientOptions: {}
clusterNodes: null
client: null
Promise: Promise
Events: null

constructor: (options={}) ->
Redis = eval("require")("ioredis") # Obfuscated or else Webpack/Angular will try to inline the optional ioredis module
parser.load options, @defaults, @
@Redis ?= eval("require")("ioredis") # Obfuscated or else Webpack/Angular will try to inline the optional ioredis module. To override this behavior: pass the ioredis module to Bottleneck as the 'Redis' option.
@Events ?= new Events @
@terminated = false

if @clusterNodes?
@client = new Redis.Cluster @clusterNodes, @clientOptions
@subscriber = new Redis.Cluster @clusterNodes, @clientOptions
@client = new @Redis.Cluster @clusterNodes, @clientOptions
@subscriber = new @Redis.Cluster @clusterNodes, @clientOptions
else if @client? and !@client.duplicate?
@subscriber = new Redis.Cluster @client.startupNodes, @client.options
@subscriber = new @Redis.Cluster @client.startupNodes, @client.options
else
@client ?= new Redis @clientOptions
@client ?= new @Redis @clientOptions
@subscriber = @client.duplicate()
@limiters = {}

Expand Down
5 changes: 3 additions & 2 deletions src/RedisConnection.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ Scripts = require "./Scripts"
class RedisConnection
datastore: "redis"
defaults:
Redis: null
clientOptions: {}
client: null
Promise: Promise
Events: null

constructor: (options={}) ->
Redis = eval("require")("redis") # Obfuscated or else Webpack/Angular will try to inline the optional redis module
parser.load options, @defaults, @
@Redis ?= eval("require")("redis") # Obfuscated or else Webpack/Angular will try to inline the optional redis module. To override this behavior: pass the redis module to Bottleneck as the 'Redis' option.
@Events ?= new Events @
@terminated = false

@client ?= Redis.createClient @clientOptions
@client ?= @Redis.createClient @clientOptions
@subscriber = @client.duplicate()
@limiters = {}
@shas = {}
Expand Down
4 changes: 2 additions & 2 deletions src/RedisDatastore.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class RedisDatastore
@capacityPriorityCounters = {}
@sharedConnection = @connection?

@connection ?= if @instance.datastore == "redis" then new RedisConnection { @clientOptions, @Promise, Events: @instance.Events }
else if @instance.datastore == "ioredis" then new IORedisConnection { @clientOptions, @clusterNodes, @Promise, Events: @instance.Events }
@connection ?= if @instance.datastore == "redis" then new RedisConnection { @Redis, @clientOptions, @Promise, Events: @instance.Events }
else if @instance.datastore == "ioredis" then new IORedisConnection { @Redis, @clientOptions, @clusterNodes, @Promise, Events: @instance.Events }

@instance.connection = @connection
@instance.datastore = @connection.datastore
Expand Down
65 changes: 39 additions & 26 deletions test/ioredis.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ if (process.env.DATASTORE === 'ioredis') {
return c.limiter.disconnect(false)
})

it('Should accept ioredis lib override', function () {
c = makeTest({
maxConcurrent: 2,
Redis,
clientOptions: {},
clusterNodes: [{
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
}]
})

c.mustEqual(c.limiter.datastore, 'ioredis')
})

it('Should connect in Redis Cluster mode', function () {
c = makeTest({
maxConcurrent: 2,
Expand Down Expand Up @@ -54,19 +68,19 @@ if (process.env.DATASTORE === 'ioredis') {
c.pNoErrVal(c.limiter.schedule(c.promise, null, 2), 2)

return c.last()
.then(function (results) {
c.checkResultsOrder([[1], [2]])
c.checkDuration(50)
c.mustEqual(c.limiter.connection.id, 'super-connection')
c.mustEqual(c.limiter.datastore, 'ioredis')

return c.limiter.disconnect()
})
.then(function () {
.then(function (results) {
c.checkResultsOrder([[1], [2]])
c.checkDuration(50)
c.mustEqual(c.limiter.connection.id, 'super-connection')
c.mustEqual(c.limiter.datastore, 'ioredis')

return c.limiter.disconnect()
})
.then(function () {
// Shared connections should not be disconnected by the limiter
c.mustEqual(c.limiter.clients().client.status, 'ready')
return connection.disconnect()
})
c.mustEqual(c.limiter.clients().client.status, 'ready')
return connection.disconnect()
})
})

it('Should accept existing redis clients', function () {
Expand All @@ -84,20 +98,20 @@ if (process.env.DATASTORE === 'ioredis') {
c.pNoErrVal(c.limiter.schedule(c.promise, null, 2), 2)

return c.last()
.then(function (results) {
c.checkResultsOrder([[1], [2]])
c.checkDuration(50)
c.mustEqual(c.limiter.clients().client.id, 'super-client')
c.mustEqual(c.limiter.connection.id, 'super-connection')
c.mustEqual(c.limiter.datastore, 'ioredis')

return c.limiter.disconnect()
})
.then(function () {
.then(function (results) {
c.checkResultsOrder([[1], [2]])
c.checkDuration(50)
c.mustEqual(c.limiter.clients().client.id, 'super-client')
c.mustEqual(c.limiter.connection.id, 'super-connection')
c.mustEqual(c.limiter.datastore, 'ioredis')

return c.limiter.disconnect()
})
.then(function () {
// Shared connections should not be disconnected by the limiter
c.mustEqual(c.limiter.clients().client.status, 'ready')
return connection.disconnect()
})
c.mustEqual(c.limiter.clients().client.status, 'ready')
return connection.disconnect()
})
})

it('Should trigger error events on the shared connection', function (done) {
Expand All @@ -117,6 +131,5 @@ if (process.env.DATASTORE === 'ioredis') {
done(err)
})
})

})
}
Loading

0 comments on commit c7575a0

Please sign in to comment.