diff --git a/README.md b/README.md index 976f688..bd75c6d 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ main() Creates a new `RetryOperation` object. `options` is the same as `retry.timeouts()`'s `options`, with three additions: -* `forever`: Whether to retry forever, defaults to `false`. +* `forever`: Whether to retry forever, defaults to `false`. Setting `retries` to `Infinity` does the same, backing off until `maxTimeout` (or over the default `10` retries without one) and then repeating that timeout. * `unref`: Whether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`. * `maxRetryTime`: The maximum time (in milliseconds) that the retried operation is allowed to run. Default is `Infinity`. @@ -116,7 +116,7 @@ milliseconds. If `options` is an array, a copy of that array is returned. `options` is a JS object that can contain any of the following keys: -* `retries`: The maximum amount of times to retry the operation. Default is `10`. Seting this to `1` means `do it once, then retry it once`. +* `retries`: The maximum amount of times to retry the operation. Default is `10`. Seting this to `1` means `do it once, then retry it once`. `retry.timeouts()` can't return endless timeouts, so for `Infinity` it returns the timeouts up to `maxTimeout`, or for the default `10` retries if there is no `maxTimeout`. * `factor`: The exponential factor to use. Default is `2`. * `minTimeout`: The number of milliseconds before starting the first retry. Default is `1000`. * `maxTimeout`: The maximum number of milliseconds between two retries. Default is `Infinity`. diff --git a/lib/retry.js b/lib/retry.js index 5e85e79..b9b9035 100644 --- a/lib/retry.js +++ b/lib/retry.js @@ -29,12 +29,25 @@ exports.timeouts = function(options) { throw new Error('minTimeout is greater than maxTimeout'); } + // An endless list of timeouts can't be built. Retry forever instead, backing off until + // maxTimeout (or over the default 10 retries without one) and then repeating the last timeout. + if (opts.retries === Infinity) { + opts.forever = true; + opts.retries = 10; + if (opts.factor <= 1) { + opts.retries = 1; + } else if (opts.maxTimeout !== Infinity) { + var steps = Math.log(opts.maxTimeout / Math.max(opts.minTimeout, 1)) / Math.log(opts.factor); + opts.retries = Math.min(Math.max(Math.ceil(steps) + 1, 1), 1000); + } + } + var timeouts = []; for (var i = 0; i < opts.retries; i++) { timeouts.push(this.createTimeout(i, opts)); } - if (options && options.forever && !timeouts.length) { + if (opts.forever && !timeouts.length) { timeouts.push(this.createTimeout(i, opts)); } diff --git a/test/integration/test-forever.js b/test/integration/test-forever.js index b41307c..0db2642 100644 --- a/test/integration/test-forever.js +++ b/test/integration/test-forever.js @@ -22,3 +22,23 @@ var retry = require(common.dir.lib + '/retry'); } }); })(); + +(function testInfiniteRetriesRetryForever() { + var operation = retry.operation({ + retries: Infinity, + minTimeout: 1, + factor: 1 + }); + + operation.attempt(function(numAttempt) { + if (operation.retry(new Error('foo'))) { + if (numAttempt === 20) { + operation.stop(); + assert.equal(operation.errors().length, 1); + } + return; + } + + assert.fail('stopped retrying after ' + numAttempt + ' attempts'); + }); +})(); diff --git a/test/integration/test-timeouts.js b/test/integration/test-timeouts.js index 7206b0f..054347e 100644 --- a/test/integration/test-timeouts.js +++ b/test/integration/test-timeouts.js @@ -67,3 +67,29 @@ var retry = require(common.dir.lib + '/retry'); var timeouts = retry.timeouts({retries: 2}); assert.strictEqual(timeouts.length, 2); })(); + +(function testInfiniteRetriesBuildFiniteTimeouts() { + var timeouts = retry.timeouts({retries: Infinity}); + + assert.equal(timeouts.length, 10); + assert.equal(timeouts[0], 1000); + assert.equal(timeouts[9], 512000); +})(); + +(function testInfiniteRetriesBackOffUntilMaxTimeout() { + var timeouts = retry.timeouts({ + retries: Infinity, + factor: 1.1, + minTimeout: 1000, + maxTimeout: 60000 + }); + + assert.equal(timeouts.length, 44); + assert.ok(timeouts[42] < 60000); + assert.equal(timeouts[43], 60000); +})(); + +(function testInfiniteRetriesWithoutGrowth() { + assert.deepEqual(retry.timeouts({retries: Infinity, factor: 1, maxTimeout: 5000}), [1000]); + assert.deepEqual(retry.timeouts({retries: Infinity, minTimeout: 5000, maxTimeout: 5000}), [5000]); +})();