Skip to content

Commit

Permalink
feat: add maxRetriesPerRequest option to limit the retries attempts p…
Browse files Browse the repository at this point in the history
…er command

#634, #61

BREAKING CHANGE:
The maxRetriesPerRequest is set to 20 instead of null (same behavior as ioredis v3)
by default. So when a redis server is down, pending commands won't wait forever
until the connection become alive, instead, they only wait about 10s (depends on the
retryStrategy option)
  • Loading branch information
luin committed Jun 24, 2018
1 parent 4e91a48 commit 1babc13
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,16 @@ This behavior can be disabled by setting the `autoResubscribe` option to `false`
And if the previous connection has some unfulfilled commands (most likely blocking commands such as `brpop` and `blpop`),
the client will resend them when reconnected. This behavior can be disabled by setting the `autoResendUnfulfilledCommands` option to `false`.

By default, all pending commands will be flushed with an error every 20 retry attempts. That makes sure commands won't wait forever when the connection is down. You can change this behavior by setting `maxRetriesPerRequest`:

```javascript
var redis = new Redis({
maxRetriesPerRequest: 1
});
```

Set maxRetriesPerRequest to `null` to disable this behavior, and every command will wait forever until the connection is alive again (which is the default behavior before ioredis v4).

### Reconnect on error

Besides auto-reconnect when the connection is closed, ioredis supports reconnecting on the specified errors by the `reconnectOnError` option. Here's an example that will reconnect when receiving `READONLY` error:
Expand Down
10 changes: 10 additions & 0 deletions lib/errors/MaxRetriesPerRequestError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = class MaxRetriesPerRequestError extends Error {
constructor (maxRetriesPerRequest) {
var message = `Reached the max retries per request limit (which is ${maxRetriesPerRequest}). Refer to "maxRetriesPerRequest" option for details.`;

super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
};

1 change: 1 addition & 0 deletions lib/errors/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.MaxRetriesPerRequestError = require('./MaxRetriesPerRequestError')
3 changes: 2 additions & 1 deletion lib/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ Redis.defaultOptions = {
keyPrefix: '',
reconnectOnError: null,
readOnly: false,
stringNumbers: false
stringNumbers: false,
maxRetriesPerRequest: 20
};

Redis.prototype.resetCommandQueue = function () {
Expand Down
16 changes: 16 additions & 0 deletions lib/redis/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var debug = require('../utils/debug')('ioredis:connection');
var Command = require('../command');
var utils = require('../utils');
var _ = require('../utils/lodash');
var {MaxRetriesPerRequestError} = require('../errors')

exports.connectHandler = function (self) {
return function () {
Expand Down Expand Up @@ -94,6 +95,21 @@ exports.closeHandler = function (self) {
self.reconnectTimeout = null;
self.connect().catch(_.noop);
}, retryDelay);

var {maxRetriesPerRequest} = self.options;
if (typeof maxRetriesPerRequest === 'number') {
if (maxRetriesPerRequest < 0) {
debug('maxRetriesPerRequest is negative, ignoring...')
} else {
var remainder = self.retryAttempts % (maxRetriesPerRequest + 1);
if (remainder === 0) {
debug('reach maxRetriesPerRequest limitation, flushing command queue...');
self.flushQueue(
new MaxRetriesPerRequestError(maxRetriesPerRequest)
);
}
}
}
};

function close() {
Expand Down
64 changes: 64 additions & 0 deletions test/functional/maxRetriesPerRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

var {MaxRetriesPerRequestError} = require('../../lib/errors')

describe('maxRetriesPerRequest', function () {
it('throw the correct error when reached the limit', function (done) {
var redis = new Redis(9999, {
retryStrategy() {
return 1
}
});
redis.get('foo', (err) => {
expect(err).instanceOf(MaxRetriesPerRequestError)
done()
})
})

it('defaults to max 20 retries', function (done) {
var redis = new Redis(9999, {
retryStrategy() {
return 1
}
});
redis.get('foo', () => {
expect(redis.retryAttempts).to.eql(21)
redis.get('foo', () => {
expect(redis.retryAttempts).to.eql(42)
done()
})
})
});

it('can be changed', function (done) {
var redis = new Redis(9999, {
maxRetriesPerRequest: 1,
retryStrategy() {
return 1
}
});
redis.get('foo', () => {
expect(redis.retryAttempts).to.eql(2)
redis.get('foo', () => {
expect(redis.retryAttempts).to.eql(4)
done()
})
})
});

it('allows 0', function (done) {
var redis = new Redis(9999, {
maxRetriesPerRequest: 0,
retryStrategy() {
return 1
}
});
redis.get('foo', () => {
expect(redis.retryAttempts).to.eql(1)
redis.get('foo', () => {
expect(redis.retryAttempts).to.eql(2)
done()
})
})
});
});

0 comments on commit 1babc13

Please sign in to comment.